前言

这应该是一个大多数都常用的请求库,因为它可以支持多种配置,跨平台实现,返回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();

Afterward
621 声望62 粉丝

努力去做,对的坚持,静待结果