浅析cancelToken

时间:2020-7-1 作者:admin

取消请求

1. 为什么要取消请求?

请求的响应时间存在不确定性,请求次数过多时,有可能较早发起的请求会较晚响应,那么我们需要设计一套机制,确保较晚发起的请求可以在客户端就取消掉较早发起的请求。

2. 如何取消一次请求:

发起一次请求A,且该请求中携带标识此次请求的id,发起一次请求B,该请求中取消请求A,请求A响应时,通过校验发现是一个被取消的请求,那么就不正常处理其响应。

3. 实际上axios是怎么做的?

针对单个请求A发多次的情形(A1,A2…An), An都会带上cancelToken,A(n+1)发起时会将An请求直接cancel(不再处理An请求响应)。

4. 具体实现

axios官网的用例

const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios.get('/user/12345', {
  cancelToken: source.token
}).catch(function (thrown) {
  if (axios.isCancel(thrown)) {
    console.log('Request canceled', thrown.message);
  } else {
    // handle error
  }
});

axios.post('/user/12345', {
  name: 'new name'
}, {
  cancelToken: source.token
})

// cancel the request (the message parameter is optional)
source.cancel('Operation canceled by the user.');

看看axios源码

// axios/lib/cancel/CancelToken.js
CancelToken.source = function source() {
  var cancel;
  var token = new CancelToken(function executor(c) {
    cancel = c;
  });
  return {
    token: token,
    cancel: cancel
  };
};

cancelToken.source()是一个工厂函数,返回了一个对象,tokencancel属性都由函数CancelToken构造而成。我们看看CancelToken构造函数做了什么:

// axios/lib/cancel/CancelToken.js
function CancelToken(executor) { // 参数executor

  // 参数类型判断为函数
  if (typeof executor !== 'function') {
    throw new TypeError('executor must be a function.');
  }

  var resolvePromise;
  // 实例挂载一个promise,这个promise会在变量resolvePromise执行后resolved
  this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve;
  });


  var token = this; // 实例即token
  // 执行器执行,将函数cancel传递到外界
  executor(function cancel(message) {
    if (token.reason) {
      // 如果token挂载了reason属性,说明该token下的请求已被取消
      return;
    }
    // token挂载reason属性
    token.reason = new Cancel(message);
    // 外界可以通过执行resolvePromise来将该token的promise置为resolved
    resolvePromise(token.reason);
  });
}

那么我们将这个token的promise resolve有什么用呢?

// axios/lib/adapters/xhr.js
    if (config.cancelToken) {
      // 请求时带上了cancelToken,如果上面token的promise resolve就会执行取消请求的操作
      config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
          return;
        }

        request.abort();
        reject(cancel);
        // Clean up request
        request = null;
      });
    }

5. 最佳实践

设计一个pendings模块,在每次请求的拦截中进行请求的自动记录取消

// pendings.js
import Axios from 'axios';

const pendings = {};

export default {
  /**
   * 添加请求
   */
  addPending(config) { 
    const { method, url, params, data } = config;
    const id = [method, url, JSON.stringify(params), JSON.stringify(data)].join('&');
    const cancel = pendings[id];
    config.cancelToken = config.cancelToken || new Axios.CancelToken(function excutor(cancel) { 
      if (!pendings[id]) { 
        pendings[id] = cancel
      }
    })
    return config;
  },

  /**
   * 移除请求
   */
  removePending(config) { 
    const { method, url, params, data } = config;
    const id = [method, url, JSON.stringify(params), JSON.stringify(data)].join('&');
    const cancel = pendings[id];
    if (cancel && typeof cancel === 'function') {
      cancel();
      pendings.delete(identify);
    } 
  },

  /**
   * 清空所有pending请求
   */
  clearPending() { 
    Object.keys(pendings).forEach(c => pending[c]());
  },
}
// request.js
axios.interceptors.request.use((config) => {
    removingPendings(config);
    addPendings(config);
    // ...
})

axios.interceptors.response.use((config) => {
    removingPendings(config);
    addPendings(config);
    // ...
})

6. 总结

cancelToken说白了就是一个promise,这个promise的状态的改变交由使用者自己决定(执行cancel函数)。届时,这个promisethen回调就会执行,取消request

7. 参考

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:87074139@qq.com 进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。