拦截fetch和重试fetch的拦截器?(Javascript)

7
我正在尝试使用 JavaScript(更具体地说是 React)创建一个拦截器来拦截 fetch 请求。它应该获取每个发起的 fetch 的返回结果,如果是 401 错误,则应该发起一个新的 fetch 调用到另一个路由来获取 cookie(刷新令牌)。然后,应该再次尝试原始的 fetch 调用(因为现在用户已经登录)。
我已经成功触发了新的 fetch 调用并为每个请求发送回了 cookie,但我遇到了以下两个问题:
  1. 我不知道如何在获取到刷新令牌后重试 fetch。这可行吗?我找到了 fetch-retry npm (https://www.npmjs.com/package/fetch-retry),但不确定我是否可以在拦截器中实现它。
  2. 我似乎在 async await 方面做错了什么(我认为),因为拦截器在返回数据之前未等待 fetch 调用(原始 fetch 的状态码似乎是 401,而不是在获取 cookie 后应该是的 200)。我还尝试在拦截器中返回 fetch 的响应,但返回值是 undefined。
有任何关于如何解决这个问题的想法吗?有人做过类似的事情吗?
以下是我的代码:
(function () {
  const originalFetch = fetch;
  fetch = function() {
      return originalFetch.apply(this, arguments).then(function(data) {

          if(data.status === 401) {
            console.log('not authorized, trying to get refresh cookie..')

            const fetchIt = async () => {
              let response = await fetch(`/api/token`, {
                  method: 'POST',
                  credentials: 'include', 
                  headers: {
                      'Content-Type': 'application/json'
                  },
              });
          }
        fetchIt();
          } 
         return data

      }); 
  };
})();

编辑:为了更清晰地说明我的目的。我需要像我上面描述的拦截器工作,这样我就不必在每次获取调用后执行以下操作:

getData() {
        const getDataAsync = async () => {
            let response = await fetch(`/api/loadData`, { method: 'POST' });

           if(response.status === 401) {
            let responseT = await fetch(`/api/token`, {
                method: 'POST',
                credentials: 'include', 
                headers: {
                    'Content-Type': 'application/json'
                },
            });

            if(responseT.status === 401) {
                return responseT.status
            }

            if(responseT.status === 200) {
            response = await fetch(`/api/loadData`, { method: 'POST' });
            }
           }

          let data = await response.json();
            //Do things with data
        };
        getDataAsync();
    };

拦截器的基本功能如下:

  1. 检查是否存在401状态码,如果是,则执行以下步骤:
  2. 获取api/token
  3. 若api/token返回401状态码,则直接返回
  4. 若api/token返回200状态码,则重新运行原始的fetch请求
3个回答

5
您可以简单地使用originalFetch获取令牌并等待响应。如果响应是401,则将空响应返回到第一个fetch调用,否则更新令牌,然后让其进入下一个条件,该条件将重新运行旧请求。

let TEMP_API = {
  '401': {
    url: 'https://run.mocky.io/v3/7a98985c-1e59-4bfb-87dd-117307b6196c',
    args: {}
  },
  '200': {
    url: 'https://jsonplaceholder.typicode.com/todos/2',
    args: {}
  },
  '404': {
    url: 'https://jsonplaceholder.typicode.com/todos/1',
    args: {
      method: "POST",
      credentials: "include"
    }
  }
}

const originalFetch = fetch;
fetch = function() {
  let self = this;
  let args = arguments;
  return originalFetch.apply(self, args).then(async function(data) {
    if (data.status === 200) console.log("---------Status 200----------");
    if (data.status === 401) {
      // request for token with original fetch if status is 401
      console.log('failed');
      let response = await originalFetch(TEMP_API['200'].url, TEMP_API['200'].args);
      // if status is 401 from token api return empty response to close recursion
      console.log("==========401 UnAuthorize.=============");
      console.log(response);
      if (response.status === 401) {
        return {};
      }
      // else set token
      // recall old fetch
      // here i used 200 because 401 or 404 old response will cause it to rerun
      // return fetch(...args); <- change to this for real scenarios
      // return fetch(args[0], args[1]); <- or to this for real sceaerios
      return fetch(TEMP_API['200'].url, TEMP_API['200'].args);
    }
    // condition will be tested again after 401 condition and will be ran with old args
    if (data.status === 404) {
      console.log("==========404 Not Found.=============");
      // here i used 200 because 401 or 404 old response will cause it to rerun
      // return fetch(...args); <- change to this for real scenarios
      // return fetch(args[0], args[1]); <- or to this for real scenarios
      return fetch(TEMP_API['200'].url, TEMP_API['200'].args);
sceaerios
    } else {
      return data;
    }
  });
};

(async function() {
  console.log("==========Example1=============");
  let example1 = await fetch(TEMP_API['404'].url, TEMP_API['404'].args);
  console.log(example1);
  console.log("==========Example2=============");
  let example2 = await fetch(TEMP_API['200'].url, TEMP_API['200'].args);
  console.log(example2);
  console.log("==========Example3=============");
  let example3 = await fetch(TEMP_API['401'].url, TEMP_API['401'].args);
  console.log(example3);
})();

  1. 请求 API 返回 404 状态码,将触发 404 状态条件,然后调用 200 API,最终返回响应。
  2. 请求 API 返回 200 状态码,将直接通过 200 状态条件并运行,然后返回响应。
  3. 请求 API 返回 401 状态码,将使 401 状态条件通过,随后调用 200 API 并打印响应,之后会退出条件,您可以在这里设置令牌,该令牌将在另一个提取请求中使用。

你好,感谢您抽出时间来尝试解决这个问题!我猜你的意思是我首先要检查刷新令牌吗?刷新令牌会访问数据库以进行验证,因此我不希望它在每个请求时都访问数据库。这就是为什么我需要它仅在访问令牌返回401时才检查刷新令牌的原因。 - Hejhejhej123
只有当响应状态为 401 时,@Hejhejhej123 才会请求令牌。如果响应状态不是 401,则它将永远不会运行。如果令牌 API 返回 401,则您需要返回以取消下一个获取重试。 - Chandan
抱歉,我刚才在阅读代码之前看到了你的评论,所以误解了。但是是否有可能使它重新运行原始的fetch而不指定URL?我希望每次调用fetch时都能运行此代码,而无需对原始fetch代码进行任何修改。 - Hejhejhej123
@Hejhejhej123,我已经更新了我的答案,并加入了临时未授权的API和解释。 - Chandan
非常感谢!它的工作方式就像我想要的一样。但是我没有得到404错误..什么时候会出现?我没有从服务器发送任何404,所以我只是使用了return并删除了if(data.status === 404)。 - Hejhejhej123

3
尝试返回fetch承诺而不是等待它。

(function () {
  const originalFetch = fetch;
  fetch = function () {
    return originalFetch.apply(this, arguments).then(function (data) {
      if (data.status === 200) console.log("---------Status 200----------");
      if (data.status === 404) {
          console.log("==========404 Not Found.=============");
          return fetch(`https://jsonplaceholder.typicode.com/todos/2`);

      } else {
        return data;
      }
    });
  };
})();

function test(id) {
  //will trigger 404 status
  return fetch(`https://jsonplaceholder.typicode.com/todos/` + id, {
    method: "POST",
    credentials: "include",
  });
}

test(1).then((i) => console.log(i));


1
谢谢!这解决了我的第二个问题并返回了正确的状态。但我还是无法解决问题1,你有什么想法吗? - Hejhejhej123

0

拦截器库 用于本地的 fetch 命令。它修补了全局的 fetch 方法,使您可以在浏览器、Node 和 Webworker 环境中使用。

fetch-retry 它可以包装任何 Fetch API 包(例如:isomorphic-fetch、cross-fetch、isomorphic-unfetch 等),并重试由于网络问题导致失败的请求。也可以配置以重试特定的 HTTP 状态码请求。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接