【问题标题】:Axios: how to cancel request inside request interceptor properly?Axios:如何正确取消请求拦截器中的请求?
【发布时间】:2018-10-31 20:55:12
【问题描述】:

如果没有令牌,我想取消请求,所以我这样做:

instance.interceptors.request.use(config => {
  if (!getToken()) {
    console.log("interceptors: no access token");
  } else {
    config.headers.Authorization = "Bearer " + getToken().accessToken;
    return config;
  }
});

但在负面情况下,会出现错误TypeError: Cannot read property 'cancelToken' of undefined

【问题讨论】:

    标签: javascript axios


    【解决方案1】:

    您不能在拦截器中使用令牌,而是抛出 Cancel

    axios.interceptors.response.use(function (response) {
      throw new axios.Cancel('Operation canceled by the user.');
    }, function (error) {
      return Promise.reject(error);
    });
    

    参考这篇文章: https://github.com/axios/axios/issues/583

    【讨论】:

    • 是否会导致function (error) {...}代码分支运行或仅在稍后落入.catch()
    • 我猜你的意思是当你抛出 axios.Cancel() 时代码会流向哪里。这个我试过了,后面直接取消catch()会出现错误。
    • 这在写作时是不正确的(你不能在拦截器中使用取消令牌)并且不能处理所有用例,例如请求已经开始并且你现在需要取消所有请求(例如,由于会话到期,用户已注销)。为此,请参阅 vijay 的解决方案。
    • OP 在请求拦截器中请求解决方案
    • 但奇怪的是,Axios 实例上似乎不存在 Cancel 方法。如果我们在 Axios 实例上使用拦截器,如何实现这一点?
    【解决方案2】:

    对于后来的google人来说,这是取自axios issue on github的一个很好的解决方案

    instance.interceptors.request.use(config => {
      /* some logic */
      return {
        ...config,
        cancelToken: new CancelToken((cancel) => cancel('Cancel repeated request'))
      };
    });
    

    这几乎是 Vijay 提出的,但形式更简洁。

    【讨论】:

    • 注意这是deprecated axios v0.22,建议使用AbortController
    • 但是怎么做呢?有没有例子? @boycy
    【解决方案3】:

    解决办法

    import axios from 'axios';
    
    const CancelToken = axios.CancelToken;
    let cancel;
    
    axios.interceptors.request.use((config) => {
    
      if (cancel) {
        cancel(); // cancel request
      }
    
      config.cancelToken =  new CancelToken(function executor(c)
        {
          cancel = c;
        })
    
      return config
    
    }, function (error) {
      return Promise.reject(error)
    });
    
    

    【讨论】:

    • 我认为这是关于取消已经运行的请求,OP 正在谈论尚未开始
    • 什么是let cancel?以及为什么要检查它是否正确然后调用它!我很困惑。
    【解决方案4】:

    @Kirill Taletski的回答完美解决了这个问题,不过加一行:

    const CancelToken = Axios.CancelToken;
    

    然后,它会是这样的:

    instance.interceptors.request.use(config => {
      /* some logic */
      const CancelToken = Axios.CancelToken;
      return {
        ...config,
        cancelToken: new CancelToken((cancel) => cancel('Cancel repeated request'))
      };
    });
    

    【讨论】:

    • 应该是评论
    【解决方案5】:

    我以这种方式实现了这一点。我不确定这是否是最好的解决方案,但对于我的用例来说很有用。 我的想法不是取消最后一个请求。我想取消以前对同一端点的请求,并让最后一个完成他的工作。因此,我会跟踪正在执行的请求。

    // I keep track of the current requests that are being executed
    const currentExecutingRequests = {};
    
    axios.interceptors.request.use(
        (req) => {
            let originalRequest = req;
    
            if (currentExecutingRequests[req.url]) {
                const source = currentExecutingRequests[req.url];
                delete currentExecutingRequests[req.url];
                source.cancel();
            }
    
            const CancelToken = axios.CancelToken;
            const source = CancelToken.source();
            originalRequest.cancelToken = source.token;
            currentExecutingRequests[req.url] = source;
    
            // here you could add the authorization header to the request
    
            return originalRequest;
        },
        (err) => {
            return Promise.reject(err);
        }
    );
    
    axios.interceptors.response.use(
        (response) => {
            if (currentExecutingRequests[response.request.responseURL]) {
                // here you clean the request
                delete currentExecutingRequests[response.request.responseURL];
            }
            return response;
        },
        (error) => {
            const { config, response } = error;
            const originalRequest = config;
    
            if (axios.isCancel(error)) {
                // here you check if this is a cancelled request to drop it silently (without error)
                return new Promise(() => {});
            }
    
            if (currentExecutingRequests[originalRequest.url]) {
                // here you clean the request
                delete currentExecutingRequests[originalRequest.url];
            }
    
            // here you could check expired token and refresh it if necessary
    
            return Promise.reject(error);
        }
    );
    

    【讨论】:

    • 能否也说明如何清理useEffect中取消的请求?
    • 你的意思是这样的吗? React.useEffect(() => { const CancelToken = axios.CancelToken; const source = CancelToken.source(); (async () => { try { const response = await axios.get(URL, { cancelToken: source.token }); console.log(response); } catch (e) { console.log(e); } })(); return () => source.cancel(); }, []);您可以在此处查看更多说明:codesandbox.io/s/cancelling-request-in-useeffect-axios-q9cze
    【解决方案6】:

    我的解决方案基于https://stackoverflow.com/a/64228288/2051938

    axios.ts

    const axiosInstance = axios.create({ baseURL: apiBaseUrl });
    
    axiosInstance.interceptors.request.use(
        req => {
            const originalRequest = req;
            const cancelUniqId = (originalRequest.cancelToken as unknown) as string;
    
            if (Object.hasOwnProperty.call(currentExecutingRequests, cancelUniqId)) {
                const source = currentExecutingRequests[cancelUniqId];
                delete currentExecutingRequests[cancelUniqId];
                source.cancel();
            }
    
            if (cancelUniqId) {
                const CancelToken = axios.CancelToken;
                const source = CancelToken.source();
                originalRequest.cancelToken = source.token;
                currentExecutingRequests[cancelUniqId] = source;
            }
    
            return originalRequest;
        },
        err => {
            return Promise.reject(err);
        }
    );
    
    axiosInstance.interceptors.response.use(
        response => {
            for (const key of Object.keys(currentExecutingRequests)) {
                if (currentExecutingRequests[key].token === response.config.cancelToken) {
                    delete currentExecutingRequests[key];
                    break;
                }
            }
            return response;
        },
        error => {
            const { response } = error;
    
            if (axios.isCancel(error)) {
                return new Promise(() => {
                    //
                });
            }
    
            for (const key of Object.keys(currentExecutingRequests)) {
                if (currentExecutingRequests[key].token === response.config.cancelToken) {
                    delete currentExecutingRequests[key];
                    break;
                }
            }
    
            return Promise.reject(error);
        }
    );
    
    export { axiosInstance };
    

    用法:

    axiosInstance.request({
        url: "some/req/path",
        method: "POST",
        params: {...},
        data: {...},
        cancelToken: "someUniqRequestID" // <-- IMPORTANT!
    })
    

    因此,如果之前使用 SAME cancelToken 的请求尚未完成,则所有使用 someUniqRequestID 令牌的请求都将被取消。

    【讨论】:

      猜你喜欢
      • 2020-10-22
      • 2020-09-25
      • 1970-01-01
      • 1970-01-01
      • 2014-04-01
      • 1970-01-01
      • 2022-11-14
      • 2019-10-04
      • 2020-02-12
      相关资源
      最近更新 更多