Axios源码:取消请求的实现

1,217 阅读3分钟

Axios

简介

Axios 是一个基于 Promise 的 HTTP 客户端,同时支持浏览器和Node.js. 拥有以下特性:

  • 支持浏览器发送AJAX请求
  • 支持Node.js发送 HTTP 请求
  • 支持Promise
  • 拦截请求和响应
  • 转换请求和响应的数据
  • 取消请求
  • 自动转换JSON数据
  • 客户端支持XSRF攻击

源码目录结构

源码主要是在lib目录下。lib目录下的目录结构

│   axios.js
│   defaults.js
│   utils.js
│
├───adapters
│       http.js
│       README.md
│       xhr.js
│
├───cancel
│       Cancel.js
│       CancelToken.js
│       isCancel.js
│
├───core
│       Axios.js
│       buildFullPath.js
│       createError.js
│       dispatchRequest.js
│       enhanceError.js
│       InterceptorManager.js
│       mergeConfig.js
│       README.md
│       settle.js
│       transformData.js
│
└───helpers
        bind.js
        buildURL.js
        combineURLs.js
        cookies.js
        deprecatedMethod.js
        isAbsoluteURL.js
        isAxiosError.js
        isURLSameOrigin.js
        normalizeHeaderName.js
        parseHeaders.js
        README.md
        spread.js

取消请求

在看取消请求之前,确保清楚axios中拦截请求是如何实现的,

简介

取消请求,首先针对要发送的请求中产生一个cancel token,然后利用该cancel token取消请求。

axios的HTTP请求是基于promise,所谓取消请求,就是改变promise的状态,由pending状态修改成reject状态

产生cancel token的方式有两种:

  • CancelToken.source 工厂函数. 实际上CancelToken.source 工厂函数是对于CancelToken  构造函数的封装
  • CancelToken  构造函数

axios的cancel token API是基于已被撤销的cancelable promises proposal.

CancelToken.source 工厂函数创建cancel token

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

CancelToken  构造函数传递一个执行程序创建cancel token

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 request
cancel();

cancelable promises proposal

虽然该proposal cancelable promises proposal 已经被撤销,但是学习它,对于了解axios如何实现取消请求还是很有帮助的。

For the one performing asynchronous work, who will accept an already-existing cancel token, there are three relevant APIs:

  • cancelToken.reason, which returns either undefined if cancelation has not been requested, or an instance of Cancel given the cancelation reason if it has
  • cancelToken.promise, which allows registration for a callback when cancelation is requested, fulfilled with a Cancel instance constructed when calling the associated cancel
  • cancelToken.throwIfRequested(), which will automatically throw the stored Cancel instance if cancelation is requested (or do nothing otherwise)

对于执行的异步工作,它将接受一个已经存在的取消令牌(token),有三个相关的API:

  • cancelToken.reason,如果没有请求取消,则返回undefined;如果请求了取消,则返回一个Cancel实例,给出取消原因
  • cancelToken.promise允许在请求取消时注册回调函数
  • canceltoken.throwifrequest(),如果请求取消,它将自动的对于存储的实例抛出异常。如果请求不取消,这个操作不会被触发

源码

cancel token

CancelToken.source 工厂函数

CancelToken.source 工厂函数生成的对象

  • token属性是一个CanelToken的实例
  • cancel属性一个待触发请求的函数
// 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
  };
};

触发cancel

执行cancel(), 触发cancel请求了。

// cancel the request
cancel();

返回一个Cancel实例,给出取消原因

 token.reason = new Cancel(message);

在请求取消时注册回调函数

  this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve;
  });
  executor(function cancel(message) {
        ...
  	resolvePromise(token.reason);
  });

// lib/adapters/xhr.js
 if (config.cancelToken) {
      // Handle cancellation
      config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
          return;
        }

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

对于存储的取消实例抛出异常

// lib/core/dispatchRequest.js
/**
 * Throws a `Cancel` if cancellation has been requested.
 */
function throwIfCancellationRequested(config) {
  if (config.cancelToken) {
    config.cancelToken.throwIfRequested();
  }
}

/**
 * Dispatch a request to the server using the configured adapter.
 *
 * @param {object} config The config that is to be used for the request
 * @returns {Promise} The Promise to be fulfilled
 */
module.exports = function dispatchRequest(config) {
  throwIfCancellationRequested(config);

  ...
  var adapter = config.adapter || defaults.adapter;

  return adapter(config).then(function onAdapterResolution(response) {
    throwIfCancellationRequested(config);

    // Transform response data
    response.data = transformData(
      response.data,
      response.headers,
      config.transformResponse
    );

    return response;
  }, function onAdapterRejection(reason) {
    if (!isCancel(reason)) {
      throwIfCancellationRequested(config);

      // Transform response data
      if (reason && reason.response) {
        reason.response.data = transformData(
          reason.response.data,
          reason.response.headers,
          config.transformResponse
        );
      }
    }

    return Promise.reject(reason);
  });
};

// lib/cancel/CancelToken.js
/**
 * Throws a `Cancel` if cancellation has been requested.
 */
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
  if (this.reason) {
    throw this.reason;
  }
};