Axios源码之Cancel功能
1708
2022.03.22
2022.03.22
发布于 未知归属地

Cancel 实现

如何使用

const CancelToken = axios.CancelToken; //获得Cancel对象
const source = CancelToken.source(); //获得cancel的token对象以及cancel方法

CancelToken.source源码,new 一个 CancelToken 对象,然后通过闭包获得内部 cancel 方法

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

Axios 也支持fetch API

const controller = new AbortController();

axios
  .get('/foo/bar', {
    signal: controller.signal,
  })
  .then(function (response) {
    //...
  });
// cancel the request
controller.abort();

CancelToken 实现

首先,闭包获得 Cancel 方法,给 executor 函数传入内部 cancal 方法

function CancelToken(executor) {
  executor(function cancel(message) {
    if (token.reason) {
      // Cancellation has already been requested
      return;
    }
    token.reason = new CanceledError(message);
    resolvePromise(token.reason);
  });
}

所以自己可以这样实现

let cancel;
axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    // An executor function receives a cancel function as a parameter
    cancel = c;
  }),
});
// cancel the request
cancel();

这样 cancel 变量就被赋值为取消函数。new CancelToken 返回值 token 是个 promise 对象,调用 cancel 后,token 状态会从 pending 变为 fullFilled,执行 fullFilled 回调即将 xhr 取消并且 reject。
CancelToken内部维护一个 listener 队列,在 xhr 封装为 Promise 对象时,会将 onCancel 函数加入该队列

// xhr.js
onCanceled = function (cancel) {
  if (!request) {
    return;
  }
  reject(!cancel || (cancel && cancel.type) ? new CanceledError() : cancel);
  request.abort();
  request = null;
};

config.cancelToken && config.cancelToken.subscribe(onCanceled);
// 注意,在请求成功后,将onCancel从队列中移出
function done() {
  if (config.cancelToken) {
    config.cancelToken.unsubscribe(onCanceled);
  }

  if (config.signal) {
    config.signal.removeEventListener('abort', onCanceled);
  }
}

CancelToken.js中,将执行 listener 逻辑,放在 promise 的 then 调用中,一旦调用 cancel 函数,promise 的状态就会从 pending 转为 fullFilled,继而执行 listener 中的取消请求逻辑。

//CancelToken.js
// eslint-disable-next-line func-names
this.promise.then(function (cancel) {
  if (!token._listeners) return;

  var i;
  var l = token._listeners.length;

  for (i = 0; i < l; i++) {
    token._listeners[i](cancel);
  }
  token._listeners = null;
});

dispatchRequest中,分别检查请求前,请求成功后,请求 reject 后是否含有config.cancelToken.reason对象,以及config.signal.aborted对象,如果有抛出CanceledError()错误,这样后续的 promise 链就会捕获到

module.exports = function dispatchRequest(config) {
  // 请求前检查是否cancel
  throwIfCancellationRequested(config);
  //....
  var adapter = config.adapter || defaults.adapter;

  return adapter(config).then(
    function onAdapterResolution(response) {
      // 请求成功时检查是否cancel
      throwIfCancellationRequested(config);

      return response;
    },
    function onAdapterRejection(reason) {
      if (!isCancel(reason)) {
        // 不是cancel的reject
        throwIfCancellationRequested(config);
      }
      return Promise.reject(reason);
    }
  );
};

基于AbortController的取消实现

  1. new 一个AbortController对象,并将AbortController.signal加入config.signal
  2. dispatchRequest中,加入config.signal.addEventListener('abort', onCanceled),监听AbortController的abort行为

AbortController是基于事件监听模式,而CancelToken也是类似的事件监听,但是使用promise的then调用,自动执行队列中的回调函数.

评论 (0)