axios的cancelToken取消机制原理

donglegend

最近封装axios,使用到取消机制,发现使用非常简便,那么axios是如何实现的呢?简单研究了一下

使用方式

axios 如何取消一个请求提供了两种使用模式:

  • 第一种 调用CancelToken的静态方法source
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.post('/user/12345', {
  name: 'new name'
}, {
  cancelToken: source.token
})
source.cancel('Operation canceled by the user.');
  • 第二种 自己实例化
let cancel;
axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    cancel = c;
  })
});
cancel();

OK,可以看到使用非常简单,两种使用方式道理是一样的,分两步:

  1. 获取cancelToken实例,注入请求的配置参数
  2. 需要取消的时候,调用 提供的cancel方法

分析

分析之前,用了几分钟思考了一下,可能是利用了状态机来控制的,具体怎么实现的,没想好。直接扒拉代码。

CancelToken.js

直接找CancelToken类,代码非常简单,如下:

function CancelToken(executor) {
  if (typeof executor !== 'function') {
    throw new TypeError('executor must be a function.');
  }

  var resolvePromise;
  this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve;
  });

  var token = this;
  executor(function cancel(message) {
    if (token.reason) {
      return;
    }

    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });
}

CancelToken.prototype.throwIfRequested = function throwIfRequested() {
  if (this.reason) {
    throw this.reason;
  }
};

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

概括一下核心逻辑就是,在实例上挂载了一个 promise,然后把 promise的实例 和 更改promise状态的 resovle函数对外抛出。

还可以看到 提供的两种使用方法并没有本质区别。

在外部,把promise实例提供给 axios的配置参数,然后更改promsie状态的resolve函数,也就是cancel函数可以随意使用。
ok,到了这里就非常明朗了,也就是一个promsie控制的极简 状态机。
我们有理由推测,axios内部拿到promise实例,只需要挂载一个then回调,回调里调用 xmlHttpRequest的abort方法即可。

那么继续追踪验证一下是不是这么实现的。

adapters/http.js

搜索关键词,可以看到第 284行,注册了回调取消函数:

 if (config.cancelToken) {
  // Handle cancellation
  config.cancelToken.promise.then(function onCanceled(cancel) {
    if (req.aborted) return;

    req.abort();
    reject(cancel);
  });
}

顺带还看到了timeout的处理:

if (config.timeout) {
  req.setTimeout(config.timeout, function handleRequestTimeout() {
    req.abort();
    reject(createError('timeout of ' + config.timeout + 'ms exceeded', config, 'ECONNABORTED', req));
  });
}

嗯,意料之中,timeout的处理机制也是一样的。

思考

看完代码,恍然大悟,原来这么简单。但是我们确不一定能写出这么优雅的代码。
这个案例,是一个简单的状态机,利用promise的异步特性,把变更内部状态的控制权移交外部,说白了就是一个闭包的应用,我们可以头头是道的讲什么是闭包,但是确不一定能运用的很巧妙,这种思维方式非常值得学习。

阅读 322

前端小记
不会写文章的程序员不是好哲学家!

长安的风何时才能吹到边梁?

856 声望
79 粉丝
0 条评论
你知道吗?

长安的风何时才能吹到边梁?

856 声望
79 粉丝
宣传栏