乐趣区

axios源码系列三-默认配置和取消请求

前言

这应该是一个大多数都常用的请求库, 因为它可以支持多种配置, 跨平台实现, 返回 promise 进行链式调用. 完全过一遍源码可以提升自己对请求库的理解知识

axios 源码系列(一) — 目录结构和工具函数
axios 源码系列(二) — 适配器内部
axios 源码系列(三) — 默认配置和取消请求
axios 源码系列(四) — Axios 和 dispatchRequest 与拦截器

默认配置

axios/lib/defaults.js

这个 Axios 库的默认参数配置

var utils = require('./utils');
var normalizeHeaderName = require('./helpers/normalizeHeaderName');

var DEFAULT_CONTENT_TYPE = {'Content-Type': 'application/x-www-form-urlencoded'};

function setContentTypeIfUnset(headers, value) {if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) {headers['Content-Type'] = value;
  }
}

function getDefaultAdapter() {
  var adapter;
  if (typeof XMLHttpRequest !== 'undefined') {
    // For browsers use XHR adapter
    adapter = require('./adapters/xhr');
  } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
    // For node use HTTP adapter
    adapter = require('./adapters/http');
  }
  return adapter;
}

这里只有四件事:

  • 引入了工具库和解析头部名函数
  • 设置默认 Content-Type 为浏览器默认的编码格式
  • 定义设置 headers 内值为空的 Content-Type 对应值的方法
  • 根据宿主环境获取对应的adapters

里面解析头部名函数的源码如下

axios/lib/helpers/normalizeHeaderName.js

过滤得出是否不同大小写字母的等价请求头, 是的话则设置新的请求头移除旧的请求头

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

module.exports = function normalizeHeaderName(headers, normalizedName) {utils.forEach(headers, function processHeader(value, name) {if (name !== normalizedName && name.toUpperCase() === normalizedName.toUpperCase()) {headers[normalizedName] = value;
      delete headers[name];
    }
  });
};

这是暴露出去的对象

var defaults = {adapter: getDefaultAdapter(),

  transformRequest: [function transformRequest(data, headers) {normalizeHeaderName(headers, 'Accept');
    normalizeHeaderName(headers, 'Content-Type');
    if (utils.isFormData(data) ||
      utils.isArrayBuffer(data) ||
      utils.isBuffer(data) ||
      utils.isStream(data) ||
      utils.isFile(data) ||
      utils.isBlob(data)
    ) {return data;}
    if (utils.isArrayBufferView(data)) {return data.buffer;}
    if (utils.isURLSearchParams(data)) {setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
      return data.toString();}
    if (utils.isObject(data)) {setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
      return JSON.stringify(data);
    }
    return data;
  }],

  transformResponse: [function transformResponse(data) {
    /*eslint no-param-reassign:0*/
    if (typeof data === 'string') {
      try {data = JSON.parse(data);
      } catch (e) {/* Ignore */}
    }
    return data;
  }],

  /**
   * A timeout in milliseconds to abort a request. If set to 0 (default) a
   * timeout is not created.
   */
  timeout: 0,

  xsrfCookieName: 'XSRF-TOKEN',
  xsrfHeaderName: 'X-XSRF-TOKEN',

  maxContentLength: -1,

  validateStatus: function validateStatus(status) {return status >= 200 && status < 300;}
};

defaults.headers = {
  common: {'Accept': 'application/json, text/plain, */*'}
};

utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) {defaults.headers[method] = {};});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE);
});

module.exports = defaults;

主要提供了几个默认配置

配置 作用
adapter 宿主环境对应的适配器
transformRequest 内置转换函数, 根据对应格式设置请求
transformResponse 内置转换函数, 如果是字符型则反序列化
timeout 超时时间
xsrfCookieName 用作 xsrf token 的值的 cookie 的名称
xsrfHeaderName 承载 xsrf token 的值的 HTTP 头的名称
maxContentLength 定义允许的响应内容的最大尺寸
validateStatus 内置对于给定的 HTTP 响应状态码是 resolve 或 reject promise 的函数
headers 默认请求头
[‘delete’, ‘get’, ‘head’] 定义相关请求方式的默认请求头{}
[post’, ‘put’, ‘patch’] 定义相关请求方式的默认请求头,{‘Content-Type’: ‘application/x-www-form-urlencoded’}

取消请求

axios/lib/cancel/Cancel.js

/**
 * A `Cancel` is an object that is thrown when an operation is canceled.
 *
 * @class
 * @param {string=} message The message.
 */
function Cancel(message) {this.message = message;}


Cancel.prototype.toString = function toString() {return 'Cancel' + (this.message ? ':' + this.message : '');
};

Cancel.prototype.__CANCEL__ = true;

module.exports = Cancel;

创建 Cancel 的构造函数, 表明取消某个请求, 提供一句取消信息和标记

axios/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 resolvePromise;
  this.promise = new Promise(function promiseExecutor(resolve) {resolvePromise = resolve;});

  var token = this;
  executor(function cancel(message) {if (token.reason) {
      // Cancellation has already been requested
      return;
    }

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

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

/**
 * 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 对象用于取消请求的操作:

  • 定义新的 Promise 并将改变状态的触发时机传递到外部
  • 执行传入的回调函数参数

    • 如果已经含有 reason 属性代表已经发出取消请求的操作, 则中断操作
    • 否则创建新的 Cancel 对象并且改变 Promise 状态返回该对象

原型上绑定 throwIfRequested 方法, 如果实例已经取消可以直接抛出取消信息

构造函数上绑定 source 方法, 调用之后可以返回新的 CancelToken 实例和 cancel 方法

axios/lib/cancel/isCancel.js

module.exports = function isCancel(value) {return !!(value && value.__CANCEL__);
};

通过是否拥有 __CANCEL__ 属性而得知某次请求是否已经取消了.

示例

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 {// 处理错误}
});

axios.post('/user/12345', {name: 'new name'}, {cancelToken: source.token})

// 取消请求(message 参数是可选的)source.cancel('Operation canceled by the user.');

或者

const CancelToken = axios.CancelToken;
let cancel;

axios.get('/user/12345', {cancelToken: new CancelToken(function executor(c) {
    // executor 函数接收一个 cancel 函数作为参数
    cancel = c;
  })
});

// cancel the request
cancel();
退出移动版