美文网首页
Axios源码阅读(三):取消请求

Axios源码阅读(三):取消请求

作者: 前端艾希 | 来源:发表于2021-12-02 19:39 被阅读0次

一、功能介绍

官方文档指出有2种方法可以取消请求,分别是cancelTokenabortController,下面是示例代码:

// method 1
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.');

// method 2
const CancelToken = axios.CancelToken;
let cancel;
axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    cancel = c;
  })
});

cancel();

// method 3
const controller = new AbortController();
axios.get('/foo/bar', {
   signal: controller.signal
}).then(function(response) {
   //...
});

controller.abort();

通过文档描述和示例代码总结出以下功能点:

  1. 支持cancelToken取消请求,cancelToken可以通过工厂函数产生,也可以通过构造函数生成;
  2. 支持Fetch APIAbortController取消请求;
  3. 一个token/signal可以取消多个请求,一个请求也可同时使用token/signal
  4. 如果在开始axios request之前执行了取消请求,则并不会发出真实的请求(见Cancellation最后一个Note);

二、源码阅读

通过阅读源码逐个了解上述功能是如何实现的。

2.1 cancelToken 取消请求

通过搜索找到axios.CancelToken = require('./cancel/CancelToken'),于是打开该文件,找到CancelToken构造函数后,发现这个函数非常的绕,这里把代码结构稍微整理下:

传送门:./lib/cancel/CancelToken.js

var Cancel = require('./Cancel');

/**
 * 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 token = this; // 把 this 保存在局部变量中

  // snippet 1
  // 给实例添加“promise”属性
  
  // snippet 2 
  // this.promise resolved 后,call所有的观察者,并且清空观察者队列
  
  // snippet 3
  // 重写 this.promise.then 方法,在 then 里面添加了订阅的代码
  
  // snippet 4
  // 执行构造函数传入的 executor,参数为一个改变 this.promise 状态的回调函数
}

上面的代码是CancelToken构造函数,这个函数实现的非常巧妙,我们逐一分析。

snippet 1

var resolvePromise;
// 给实例添加 promise 属性,并且把 resolve 保存在局部变量中
this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve;
});

这里把resolve保存在局部变量中为一个关键步骤,相当于保存了一把“钥匙”,以后可以随时用这把钥匙改变this.promise的状态继而引发观察者的action

snippet 2

this.promise.then(function(cancel) {
    // 通过上下文分析,这里传入的 cancel 为取消请求的原因对象,例如:{message: 'reason'}
    if (!token._listeners) return;

    var i;
    var l = token._listeners.length;
    // 将观察者队列清空
    for (i = 0; i < l; i++) {
      token._listeners[i](cancel);
    }
    token._listeners = null;
});

这里代码的功能是在请求被取消之后,执行所有之前所有订阅“取消”状态的方法,这里的代码也很关键,因为在xhr.js中需要根据该状态去执行xhr.abort,代码如下:

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;
    };
    // 这里 onCanceled 订阅了取消事件
    config.cancelToken && config.cancelToken.subscribe(onCanceled);
    if (config.signal) {
        config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
    }
}

上面的代码在./lib/adapters/xhr.js中,cancelToken.subscribe方法会把onCanceled放到cancelToken实例的_listeners中。这里使用了观察者模式,使用户可以很方便的添加取消事件的回调。

snippet 3

this.promise.then = function(onfulfilled) {
    var _resolve;
    // eslint-disable-next-line func-names
    var promise = new Promise(function(resolve) {
        token.subscribe(resolve);
        _resolve = resolve;
    }).then(onfulfilled);

    promise.cancel = function reject() {
        token.unsubscribe(_resolve);
    };

    return promise;
};

这里的代码重写了token.promise.then方法,当调用者后续调用then方法时,添加的方法可以直接添加到实例的_listeners数组中,这个和直接调用subscribe的区别是,这样订阅的方法是异步执行的。

snippet 4

// 执行构造函数传入的 function,并且传入一个 cancel 方法
executor(function cancel(message) {
    if (token.reason) {
      // Cancellation has already been requested
      return;
    }

    token.reason = new Cancel(message);
    // 改变 token.promise 的状态
    resolvePromise(token.reason);
});

在构造函数CancelToken执行时就执行传入的function,我认为这样做主要是为了在构造函数内对外暴露一个接口,可以通过这个方法访问构造函数内部变量。也是因为这些代码,所以支持config.cancelToken = new CancelToken

CancelToken.source

示例代码中提到使用source.cancel取消请求,这里看下这个功能是如何实现的:

/**
 * 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
  };
};

可以看到CancelToken.source返回一个对象,cancel就是在CancelToken构造函数内executor执行时传入的参数。

2.2 AbortController 取消请求

在这之前,我对AbortController没什么了解,所以先看下MDN-AbortController。看完后,我将其简单理解为abort事件控制器,因为其只支持abort事件,我们通过观察实例上的signal状态便能知道请求是否被取消。我们找到相关代码如下:

传送门:./lib/adapters/xhr.js

onCanceled = function(cancel) {
    if (!request) {
        return;
    }
    reject(!cancel || (cancel && cancel.type) ? new Cancel('canceled') : cancel);
    request.abort();
    request = null;
 };
if (config.signal) {
    config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
}

xhr.send前监听了signal的状态,我们调用controller.abort后,执行onCanceled取消xhr请求。

2.3 一个请求订阅多个事件

因为CancelToken是基于已撤销的提案,所以Axiosv0.22.2使用AbortController,为了兼容以前的代码,现在库中仍然保留了相关代码,在原来使用CancelToken的地方使用了||确保两者都起作用。

2.4 多个请求观察一个对象

能实现这个功能是因为代码采取了良好的设计。

基于 CancelToken

每个token实例都有一个_listeners数组,当每个请求的adapter执行的时候都会往_listeners压入一个观察回调,当token.promise凝固后就会执行所有的观察回调。

基于 AbortController

使用AbortController就更简单了,这个相当于是原生实现观察者模式。

三、总结

个人感觉cancellation这部分代码是最绕的,又因为各种异步代码,所以有些地方光凭看很难知道到底是怎么执行的,所以我们不妨运行Axios提供的测试代码,然后再浏览器中本地调试,通过打断点查看执行请情况。

通过这次源码阅读,我了解了:

  1. Axios是如何取消请求的;
  2. 可以使用token.promise.then添加观察者,也可以使用token.subscribe。不同的是前者添加的观察者是异步执行,而后者添加的观察者是同步执行;
  3. AbortController对象;
  4. 使用function作为参数访问函数内部变量。

相关文章

  • Axios源码阅读(三):取消请求

    一、功能介绍 官方文档[https://axios-http.com/docs/cancellation]指出有2...

  • axios如何取消接口请求

    vue项目,如何在axios中取消已经发送的请求呢? 原生js的abort()这个方法 在axios中取消接口请求...

  • axios 取消请求

    axios文档里介绍的取消axios请求有以下两种方式: 举?:切换路由时,取消上个路由的请求。 其实我们的解决方...

  • axios取消请求?

    前言 最近在项目中遇到一个问题,在连续发送同一请求时,如果第二次请求比第一次请求快,那么实际显示的是第一次请求的数...

  • 防止发起多余http请求的几种方式

    cancelToken 场景: 请求接口数据量过大,结果未返回需要取消接口pending用法:axios取消请求的...

  • 【axios源码】请求取消的实现

    一、如何设计一个取消请求的功能 取消请求是通过xhr.abort()这样实现的。但是不可能将xhr变量暴露出来,因...

  • Axios取消功能源码阅读

    前言 axios库提供了取消请求的功能,虽然个人平时很少用到。 正文 下面是官网的两个例子: 第一个例子中,在ax...

  • 问题总结

    1. 取消axios请求 业务场景:单页应用,希望退出当前页的时候,取消请求 官网: https://github...

  • axios取消请求以及获取文件上传进度

    1. axios取消接口请求[https://segmentfault.com/a/11900000212905...

  • axios-取消请求

    点击cancel函数就可以阻止请求。

网友评论

      本文标题:Axios源码阅读(三):取消请求

      本文链接:https://www.haomeiwen.com/subject/lcluxrtx.html