Axios 文档案例

先看下 Axios 文档给的例子 https://github.com/axios/axio...

  1. 通过 CancelToken.source 工厂函数进行勾销
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.');
  1. 通过 CancelToken 构造函数进行勾销
const CancelToken = axios.CancelToken;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 requestcancel();
  1. 通过 AbortController 中断请求,这是 fetch 的 api,本文就不再具体介绍了,具体应用能够参考 https://developer.mozilla.org...
const controller = new AbortController();axios.get('/foo/bar', {   signal: controller.signal}).then(function(response) {   //...});// cancel the requestcontroller.abort()

源码剖析

首先须要从 GitHub 下载 Axios 源码。如果不想下载,也能够关上 https://github1s.com/axios/ax... 进行查看。

工厂函数 CancelToken.source

通过后面两个例子,能够晓得勾销申请和 CancelToken 这个类非亲非故,CancelToken.source()工厂函数只不过是在咱们看不见的外部帮忙咱们去实例化一个 CancelToken 的实例进去。

那咱们先来看下工厂函数的实现。

// 文件门路 Axios/lib/cancel/CancelToken.js// .../** * Returns an object that contains a new `CancelToken` and a function that, when called, * cancels the `CancelToken`. */CancelToken.source = function source() {  var cancel;  var token = new CancelToken(function executor(c) {    cancel = c;  });  return {    token: token,    cancel: cancel  };};module.exports = CancelToken;

能够看到 CancelToken.source 这个工厂函数就是帮忙咱们实例化了一个 CancelToken 的实例,而后返回给咱们须要应用的 实例(token)勾销申请的函数(cancel)

接下来咱们持续深刻 CancelToken 外部,看看为什么执行了 cancel 函数后,申请就中断了。

CancelToken 类

// 文件门路 Axios/lib/cancel/CancelToken.js// .../** * A `CancelToken` is an object that can be used to request cancellation of an operation. * * @class * @param {Function} executor The executor function. */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;  // 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;  });  // eslint-disable-next-line func-names  this.promise.then = function(onfulfilled) { // ...  };  executor(function cancel(message) {    if (token.reason) {      // Cancellation has already been requested      return;    }    token.reason = new Cancel(message);    resolvePromise(token.reason);  });}// ...

通过文档中的例子能够看出,在发动申请的时候传入了 cancelToken,也就是 CancelToken 的一个实例。

实例化的过程中会调用咱们传入的 executor函数,将 cancel 函数传递给咱们内部。

另外这个实例上有一个 promise 的属性,当咱们调用 cancel 函数,promise 则会从 pending 的状态变成 fulfilled。从而触发 promise.then,执行所有的 token._listeners

token._listeners 又从何而来?

答案还是在以后的文件中

// 文件门路 Axios/lib/cancel/CancelToken.js// .../** * Subscribe to the cancel signal */CancelToken.prototype.subscribe = function subscribe(listener) {  // reason 值不为 undefined 阐明该申请已勾销,可间接调用 listener  if (this.reason) {    listener(this.reason);    return;  }  if (this._listeners) {    this._listeners.push(listener);  } else {    this._listeners = [listener];  }};// ...

CancelToken 的原型对象上增加了 subscribe 办法,用于订阅勾销申请的事件。如果该申请已被勾销,则会立刻调用 listener,否则会将 listener 保留在 _listeners 数组中。

当咱们调用 cancel 也就是勾销申请的时候,_listeners 中保留的 listener 则会被调用(见上文)。

这时候并没有看到中断请求的操作,具体的逻辑是在 listener 外部,这样写的起因就是能够进行解耦,进步代码的复用性。

另外还有一个 unsubscribe 勾销订阅就不再开展了。

这就是典型的订阅公布模式。

勾销申请

最疾速的办法就是搜寻 config.cancelToken.subscribe,这样就能够疾速定位到勾销申请的具体实现。

只搜寻 lib 文件夹即可,能够看到有两处中央,一个是 lib/adapters/http.js,另一个是 lib/adapters/xhr.js

因为 Axios 是一个反对 node.js 和浏览器的 http 客户端。这里利用了适配器的模式来兼容这两个平台。本文钻研的是勾销申请,就不去深究这部分了,咱们看其中之一就好了。

// Axios/lib/adapters/xhr.js// ...    if (config.cancelToken || config.signal) {      // Handle cancellation      // eslint-disable-next-line func-names      onCanceled = function(cancel) {        if (!request) {          return;        }        reject(!cancel || (cancel && cancel.type) ? new Cancel('canceled') : cancel);        request.abort();        request = null;      };      config.cancelToken && config.cancelToken.subscribe(onCanceled);      if (config.signal) {        config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);      }    }    // ...

从 187 行这里开始,咱们能够看到 config.cancelToken.subscribe(onCanceled)cancelToken 注册了一个中断请求的回调。request.abort(); 这里的 requestXMLHttpRequest 的一个实例。

另外还有一个函数 done,即申请胜利或者失败之后会将下面注册的 onCanceled 进行勾销注册。

至此整个勾销申请的逻辑就跑通了。我简略画了个图(画了几个小时),心愿能不便大家了解。

联合 Vue 实现来到页面勾销未实现的申请

思路就是利用一个对象来治理所有的 CancelToken 实例。发动申请之前,把新创建的 cancelToken 保留到对象中,申请完结后(包含胜利、失败)把对应的实例革除。

再联合 vue-router 的路由守卫,这样就能够在来到页面的时候勾销所有未实现的申请。

有些全局的接口须要做非凡解决,比方申请用户信息之类的,这些全局通用的接口就不能再来到页面的时候中断请求。

具体代码这里就不展现了。我写了一个 demo,有须要的小伙伴能够自行查看。

https://github.com/AD-feiben/...

最初再重申一点,学习源码是为了学习源码中优良的设计,咱们须要思考如何将这个设计利用到咱们的我的项目中,这才是最重要的一点。

心愿文章的内容能为你提供一丝丝帮忙,如果谬误,还望斧正。