# 📁 lib # |—— 📁 adapters // axios主要使用的请求方法 # |—— |—— 📃 http.js // axios中node端使用的请求函数 # |—— |—— 📃 xhr.js // axios中浏览器端使用的请求函数 # |—— 📁 cancel # |—— |—— 📃 Cancel.js // 定义了,取消请求返回的信息结构 # |—— |—— 📃 CancelToken.js // 定义了用于取消请求的主要方法 # |—— |—— 📃 isCancel.js // 判断是否是取消请求的信息 # |—— 📁 core # |—— |—— 📃 Axios.js // Axios类 # |—— |—— 📃 dispatchRequest.js // 发起请求的地方 # |—— |—— 📃 InterceptorManager.js // InterceptorManager类,拦截器类 # |—— |—— 📃 mergeConfig.js // 合并配置项 # |—— |—— 📃 settle.js // 根据请求状态,处理Promise # |—— |—— 📃 createError.js // 生成指定的 error # |—— |—— 📃 enhanceError.js // 指定 error 对象的toJSON方法 # |—— |—— 📃 transformData.js // 使用default.js中transformRequest和transformResponse对响应以及请求进行格式化 # |—— 📁 helpers # |—— |—— 📃 bind.js // 工具函数 # |—— |—— 📃 parseHeaders.js // 将getAllResponseHeaders返回的header信息转化为对象 # |—— |—— 📃 buildURL.js // 将params参数 # |—— |—— 📃 cookies.js // 封装了读取,写入,删除cookies的方法 # |—— |—— 📃 isURLSameOrigin.js // 检测当前的url与请求的url是否同源 # |—— |—— 📃 normalizeHeaderName.js // 对对象属性名的进行格式化,删除,新建符合大小写规范的属性 # |—— |—— 📃 combineURLs.js // 组合baseurl # |—— |—— 📃 isAbsoluteURL.js // 判断是否为绝对路径(指的://或//开头的为绝对路径) # |—— 📃 axios.js # |—— 📃 defaults.js // axios中默认配置 # |—— 📃 utils.js // 一些工具方法 # |—— |—— ⏹ isFormData // 判断是否是formData对象 # |—— |—— ⏹ isStandardBrowserEnv // 判断当前环境是否为标准浏览器环境 # |—— |—— ⏹ isUndefined // 判断是否为undefined # |—— |—— ⏹ merge # |—— |—— ⏹ isURLSearchParams // 判断是否为URLSearchParams对象 复制代码

前言

本文主要关注axios中主流程的源码,对于一些工具函数的实现会略过。还请见谅。如果文章中有错误的地方,还请及时指出。

请求流程概览

下面是axios源码中发起一个请求时代码大致的流程

源码逐行分析

/lib/cancel/CancelToken.js

CancelToken.js中定义了取消axios请求的相关行为的代码。但CancelToken.source返回的取消请求的cancel方法,使用的前提,是需要将CancelToken.source返回token的,结合到具体的请求的config中才能正常使用。

如何在axios中使用取消请求的功能?

我在看axios源码之前,甚至并不知道axios可以发出的请求,所以我们先来了解下如何在axios取消一个请求。下面是一个例子🌰

// axios用于取消请求的类
const CancelToken = axios.CancelToken
// source方法会返回一个对象,对象包含
  // token, 添加到请求的config,用于标识请求
  // cancel, 调用cancel方法取消请求
const source = CancelToken.source()
axios.get('/info', {
  cancelToken: source.token
}).catch(function(error) {
  if (axios.isCancel(error)) {
    console.log('取消请求的错误')
  } else {
    // 其他错误
// 调用source.cancel可以取消axios.get('/info')的请求
source.cancel('取消请求')
复制代码

源码

var Cancel = require('./Cancel');
function CancelToken(executor) {
  if (typeof executor !== 'function') {
    throw new TypeError('executor must be a function.');
  var resolvePromise;
  // 创建一个Promise
  // 在调用cancel函数前该promise会一直处于pending状态
  this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve;
  var token = this;
  executor(function cancel(message) {
    // 判断是否已经取消请求了
    if (token.reason) {
      return;
    // 创建取消请求的信息,并将信息添加到实例的reason属性上
    token.reason = new Cancel(message);
    // 结束this.promise的pending状态
    // 将this.promise状态设置为resolve
    resolvePromise(token.reason);
// 判断该请求是否已经被取消的方法
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
  if (this.reason) {
    throw this.reason;
CancelToken.source = function source() {
  var cancel;
  var token = new CancelToken(function executor(c) {
    cancel = c;
  return {
    token: token,
    cancel: cancel
module.exports = CancelToken;
复制代码

🤔 看到这里,我们还是无法了解axios是如何取消一个请求的。因为单独使用CancelToken.source返回的cancel是无法取消一个请求的,我们需要结合xhr.js中的代码来理解。

// /lib/adapters/xhr.js request .open () // ...省略 // 如果配置了cancelToken选项 if (config.cancelToken) { // 对CancelToken中创建的Promise添加成功的回调 // 当调用CancelToken.source暴露的cancel函数时,回调会被触发 config .cancelToken .promise .then (function onCanceled(cancel) { if (!request) { return; // 取消xhr请求 request .abort (); // 将axios返回的promise,置为reject态 reject (cancel); request = null; // ...省略 request .send () 复制代码

想必大家看到这里,对axios中如何请求有了一个大致的了解。我们来总结一下,我们通过 CancelToken,创建了一个额外的PromiseA,并将PromiseA挂载到config中,同时将该PromiseA的resolve方法暴露出去。我们在调用send方法前(发送请求前)添加对PromiseA的状态进行监听,当PromiseA的状态被修改,我们会在PromiseA的callback中取消请求,并且将axios返回的PromiseB的状态置为reject。从而达到取消请求的目的

/lib/adapters/xhr.js

xhr.js导出的xhrAdapter方法是axios在浏览器环境下使用的默认请求方法。我们可以在配置中使用 adapter 配置项对默认请求方法进行替换。

module . exports = function xhrAdapter ( config ) { return new Promise ( function dispatchXhrRequest ( resolve, reject ) { var requestData = config. data ; var requestHeaders = config. headers ; // 判断是否是FormData对象, 如果是, 删除header的Content-Type字段,让浏览器自动设置Content-Type字段 if (utils. isFormData (requestData)) { delete requestHeaders[ 'Content-Type' ]; // 创建xtr对象 var request = new XMLHttpRequest (); // 设置http请求头中的Authorization字段 // 关于Authorization字段 // 更多内容参考https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Authorization if (config. auth ) { var username = config. auth . username || '' ; var password = config. auth . password || '' ; // 使用btoa方法base64编码username和password requestHeaders. Authorization = 'Basic ' + btoa (username + ':' + password); // 初始化请求方法 // open(method: 请求的http方法, url: 请求的url地址, 是否支持异步) request. open ( config. method . toUpperCase (), buildURL (config. url , config. params , config. paramsSerializer ), // 设置超时时间 request. timeout = config. timeout ; // 监听readyState状态的变化,当readyState状态为4的时候,表示ajax请求成功 request. onreadystatechange = function handleLoad ( ) { if (!request || request. readyState !== 4 ) { return ; // request.status响应的数字状态码,在完成请求前数字状态码等于0 // 如果request.status出错返回的也是0,但是file协议除外,status等于0也是一个成功的请求 // 更多内容请参考 https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/status if (request. status === 0 && !(request. responseURL && request. responseURL . indexOf ( 'file:' ) === 0 )) { return ; // getAllResponseHeaders方法会返回所有的响应头 // 更多内容请参考 https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/getAllResponseHeaders var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders (request. getAllResponseHeaders ()) : null ; // 如果没有设置数据响应类型(默认为“json”)或者responseType设置为text时,获取request.responseText值否则是获取request.response // responseType是一个枚举类型,手动设置返回数据的类型 更多请参考 https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/responseType // responseText是全部后端的返回数据为纯文本的值 更多请参考 https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/responseText // response为正文,response的类型取决于responseType 更多请参考 https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/response var responseData = !config. responseType || config. responseType === 'text' ? request. responseText : request. response ; var response = { data : responseData, // 响应正文 status : request. status , // 响应状态 statusText : request. statusText , // 响应状态的文本信息 headers : responseHeaders, // 响应头 config : config, request : request // status >= 200 && status < 300 resolve // 否则reject settle (resolve, reject, response); request = null ; // ajax中断时触发 request. onabort = function handleAbort ( ) { if (!request) { return ; // 抛出Request aborted错误 reject ( createError ( 'Request aborted' , config, 'ECONNABORTED' , request)); request = null ; // ajax失败时触发 request. onerror = function handleError ( ) { // 抛出Network Error错误 reject ( createError ( 'Network Error' , config, null , request)); request = null ; // ajax请求超时时调用 request. ontimeout = function handleTimeout ( ) { // 抛出 timeout错误 reject ( createError ( 'timeout of ' + config. timeout + 'ms exceeded' , config, 'ECONNABORTED' , request)); request = null ; // 判断当前是为标准浏览器环境,如果是,添加xsrf头 // 什么是xsrf header? xsrf header是用来防御CSRF攻击 // 原理是服务端生成一个XSRF-TOKEN,并保存到浏览器的cookie中,在每次请求中ajax都会将XSRF-TOKEN设置到request header中 // 服务器会比较cookie中的XSRF-TOKEN与header中XSRF-TOKEN是否一致 // 根据同源策略,非同源的网站无法读取修改本源的网站cookie,避免了伪造cookie if (utils. isStandardBrowserEnv ()) { var cookies = require ( './../helpers/cookies' ); // withCredentials设置跨域请求中是否应该使用cookie 更多请参考 https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/withCredentials // (设置了withCredentials为true或者是同源请求)并且设置xsrfCookieName var xsrfValue = (config. withCredentials || isURLSameOrigin (config. url )) && config. xsrfCookieName ? // 读取cookie中XSRF-TOKEN cookies. read (config. xsrfCookieName ) : undefined ; if (xsrfValue) { // 在request header中设置XSRF-TOKEN requestHeaders[config. xsrfHeaderName ] = xsrfValue; // setRequestHeader是用来设置请求头部的方法 if ( 'setRequestHeader' in request) { // 将config中配置的requestHeaders,循环设置到请求头上 utils. forEach (requestHeaders, function setRequestHeader ( val, key ) { if ( typeof requestData === 'undefined' && key. toLowerCase () === 'content-type' ) { delete requestHeaders[key]; } else { request. setRequestHeader (key, val); // 设置xhr对象的withCredentials属性,是否允许cookie进行跨域请求 if (config. withCredentials ) { request. withCredentials = true ; // 设置xhr对象的responseType属性 if (config. responseType ) { try { request. responseType = config. responseType ; } catch (e) { if (config. responseType !== 'json' ) { throw e; // 下载进度 if ( typeof config. onDownloadProgress === 'function' ) { request. addEventListener ( 'progress' , config. onDownloadProgress ); // 上传进度 // request.upload XMLHttpRequest.upload 属性返回一个 XMLHttpRequestUpload对象,用来表示上传的进度 if ( typeof config. onUploadProgress === 'function' && request. upload ) { request. upload . addEventListener ( 'progress' , config. onUploadProgress ); if (config. cancelToken ) { // 取消请求,在介绍/lib/cancel/CancelToken.js中以及介绍,这里不在赘述 config. cancelToken . promise . then ( function onCanceled ( cancel ) { if (!request) { return ; request. abort (); reject (cancel); request = null ; if (requestData === undefined ) { requestData = null ; // 发送http请求 request. send (requestData); 复制代码

/lib/core/dispatchRequest.js

dispatchRequest.js文件是axios源码中实际调用请求的地方。

var utils = require('./../utils');
var transformData = require('./transformData');
var isCancel = require('../cancel/isCancel');
var defaults = require('../defaults');
var isAbsoluteURL = require('./../helpers/isAbsoluteURL');
var combineURLs = require('./../helpers/combineURLs');
// 判断请求是否已被取消,如果已经被取消,抛出已取消
function throwIfCancellationRequested(config) {
  if (config.cancelToken) {
    config.cancelToken.throwIfRequested();
module.exports = function dispatchRequest(config) {
  throwIfCancellationRequested(config);
  // 如果包含baseUrl, 并且不是config.url绝对路径,组合baseUrl以及config.url
  if (config.baseURL && !isAbsoluteURL(config.url)) {
    // 组合baseURL与url形成完整的请求路径
    config.url = combineURLs(config.baseURL, config.url);
  config.headers = config.headers || {};
  // 使用/lib/defaults.js中的transformRequest方法,对config.headers和config.data进行格式化
  // 比如将headers中的Accept,Content-Type统一处理成大写
  // 比如如果请求正文是一个Object会格式化为JSON字符串,并添加application/json;charset=utf-8的Content-Type
  // 等一系列操作
  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  // 合并不同配置的headers,config.headers的配置优先级更高
  config.headers = utils.merge(
    config.headers.common || {},
    config.headers[config.method] || {},
    config.headers || {}
  // 删除headers中的method属性
  utils.forEach(
    ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
    function cleanHeaderConfig(method) {
      delete config.headers[method];
  // 如果config配置了adapter,使用config中配置adapter的替代默认的请求方法
  var adapter = config.adapter || defaults.adapter;
  // 使用adapter方法发起请求(adapter根据浏览器环境或者Node环境会有不同)
  return adapter(config).then(
    // 请求正确返回的回调
    function onAdapterResolution(response) {
      // 判断是否以及取消了请求,如果取消了请求抛出以取消
      throwIfCancellationRequested(config);
      // 使用/lib/defaults.js中的transformResponse方法,对服务器返回的数据进行格式化
      // 例如,使用JSON.parse对响应正文进行解析
      response.data = transformData(
        response.data,
        response.headers,
        config.transformResponse
      return response;
    // 请求失败的回调
    function onAdapterRejection(reason) {
      if (!isCancel(reason)) {
        throwIfCancellationRequested(config);
        if (reason && reason.response) {
          reason.response.data = transformData(
            reason.response.data,
            reason.response.headers,
            config.transformResponse
      return Promise.reject(reason);
复制代码

/lib/core/InterceptorManager.js

InterceptorManager.js文件中定义了axios拦截器类。包含了拦截器的添加,删除,循环拦截器。无论是响应拦截器还是请求拦截器,都是使用数组进行存储的。

var utils = require('./../utils');
// 拦截器类
function InterceptorManager() {
  // handlers数组用来存储拦截器
  this.handlers = [];
// 添加拦截器,use方法接收两个参数,成功的回调以及失败的回调
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    // 成功的回调
    fulfilled: fulfilled,
    // 失败的回调
    rejected: rejected
  return this.handlers.length - 1;
// 根据id(索引),删除实例handlers属性中拦截器
InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null;
// 循环拦截器
InterceptorManager.prototype.forEach = function forEach(fn) {
  utils.forEach(this.handlers, function forEachHandler(h) {
    if (h !== null) {
      fn(h);
module.exports = InterceptorManager;
复制代码

/lib/core/Axios.js

Axios.js文件中定义了Axios实例上的request,get,post,delete方法。get,post,delete等方法均是基于Axios.prototype.request的封装📦。在Axios.prototype.request中会依次执行请求拦截器,dispatchRequest(实际发起),响应拦截器。整体的流程如👆上图所示。

var utils = require('./../utils');
var buildURL = require('../helpers/buildURL');
var InterceptorManager = require('./InterceptorManager');
var dispatchRequest = require('./dispatchRequest');
var mergeConfig = require('./mergeConfig');
function Axios(instanceConfig) {
  // Axios的配置
  this.defaults = instanceConfig;
  // 拦截器
  this.interceptors = {
    request: new InterceptorManager(), // 请求拦截器
    response: new InterceptorManager() // 响应拦截器
Axios.prototype.request = function request(config) {
  // 如果config是一个字符串,把字符串当作请求的url地址
  if (typeof config === 'string') {
    config = arguments[1] || {};
    config.url = arguments[0];
  } else {
    config = config || {};
  // 合并配置
  config = mergeConfig(this.defaults, config);
  // 如果没有指定请求方法,使用get方法
  config.method = config.method ? config.method.toLowerCase() : 'get';
  var promise = Promise.resolve(config);
  // 将请求拦截器,和响应拦截器,以及实际的请求(dispatchRequest)的方法组合成数组,类似如下的结构
  // [请求拦截器1success, 请求拦截器1error, 请求拦截器2success, 请求拦截器2error, dispatchRequest, undefined, 响应拦截器1success, 响应拦截器1error]
  var chain = [dispatchRequest, undefined];
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  // 开始执行整个请求流程(请求拦截器->dispatchRequest->响应拦截器)
  // 流程可以理解为上图⬆️
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  return promise;
Axios.prototype.getUri = function getUri(config) {
  config = mergeConfig(this.defaults, config);
  return buildURL(config.url, config.params, config.paramsSerializer).replace(/^\?/, '');
// 基于Axios.prototype.request封装其他方法
// 将delete,get,head,options,post,put,patch添加到Axios.prototype的原型链上
// Axios.prototype.delete =
// Axios.prototype.get =
// Axios.prototype.head =
// Axios.prototype.options =
// ...
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  Axios.prototype[method] = function(url, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url
    }));
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  Axios.prototype[method] = function(url, data, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url,
      data: data
    }));
module.exports = Axios;
复制代码

/lib/defaults.js

defaults.js文件中配置了,axios默认的请求头、不同的环境下axios默认使用的请求方法、格式化请求正文的方法,格式化响应正文方法等内容

var utils = require('./utils');
var normalizeHeaderName = require('./helpers/normalizeHeaderName');
// 默认Content-Type
var DEFAULT_CONTENT_TYPE = {
  'Content-Type': 'application/x-www-form-urlencoded'
// 设置ContentType,在没有设置的情况下
function setContentTypeIfUnset(headers, value) {
  if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) {
    headers['Content-Type'] = value;
// 根据当前环境,获取默认的请求方法
function getDefaultAdapter() {
  var adapter;
  // 判断当前环境是否存在process对象
  if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
    // node环境
    adapter = require('./adapters/http');
  } else if (typeof XMLHttpRequest !== 'undefined') {
    // 浏览器环境
    adapter = require('./adapters/xhr');
  return adapter;
var defaults = {
  // 默认的请求方法
  adapter: getDefaultAdapter(),
  // 格式化请求requestData,这会请求发送前使用
  transformRequest: [
    function transformRequest(data, headers) {
      // 格式化header属性名,将header中不标准的属性名,格式化为Accept属性名
      normalizeHeaderName(headers, 'Accept');
      // 格式化header属性名,将header中不标准的属性名,格式化为Content-Type属性名
      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;
      // URLSearchParams提供了一些用来处理URL查询字符串接口
      // 如果是URLSearchParams对象
      if (utils.isURLSearchParams(data)) {
        // Content-Type设置为application/x-www-form-urlencoded
        // application/x-www-form-urlencoded,数据被编码成以&分隔的键值对
        setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
        return data.toString();
      // 如果是对象
      if (utils.isObject(data)) {
        // Content-Type设置为application/json
        setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
        // 将请求正文格式化为JSON字符串,并返回
        return JSON.stringify(data);
      return data;
  // 格式化响应resposeData,这会响应接受后使用
  transformResponse: [
    function transformResponse(data) {
      if (typeof data === 'string') {
        try {
          data = JSON.parse(data);
        } catch (e) { /* Ignore */ }
      return data;
  // 默认超时时间
  timeout: 0,
  // xsrf设置的cookie的key
  xsrfCookieName: 'XSRF-TOKEN',
  // xsrf设置header的key
  xsrfHeaderName: 'X-XSRF-TOKEN',
  maxContentLength: -1,
  // 验证请求的状态
  // 在处理请求的Promise会被使用
  validateStatus: function validateStatus(status) {
    return status >= 200 && status < 300;
defaults.headers = {
  // 通用的HTTP字段
  // Accept告知客户端可以处理的类型
  common: {
    'Accept': 'application/json, text/plain, */*'
utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) {
  defaults.headers[method] = {};
// 为post,put,patch请求设置默认的Content-Type
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE);
module.exports = defaults;
复制代码

/lib/axios.js

axios.js文件是axios工具库的入口方法,在axios.js

var utils = require('./utils');
var bind = require('./helpers/bind');
var Axios = require('./core/Axios');
var mergeConfig = require('./core/mergeConfig');
var defaults = require('./defaults');
// 创建axios实例
function createInstance(defaultConfig) {
  var context = new Axios(defaultConfig);
  // 更改Axios.prototype.request的this,执行context实例
  // instance等于Axios.prototype.request方法
  var instance = bind(Axios.prototype.request, context);
  // 将Axios.prototype,context上的属性合并到instance
  // instance.get = Axios.prototype.get
  // instance.defaults = context.defaults
  // ...
  utils.extend(instance, Axios.prototype, context); 
  utils.extend(instance, context);
  return instance;
// axios会直接对使用者暴露一个axios.request的方法,所以我们在使用axios的时候可以这样使用。不需要new一个axios的实例
// import axios from 'axios'
// axios.get('/info')
var axios = createInstance(defaults);
axios.Axios = Axios;
// axios.create可以根据用户自定义的config生成一个新的axios实例
axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');
axios.all = function all(promises) {
  return Promise.all(promises);
axios.spread = require('./helpers/spread');
module.exports = axios;
module.exports.default = axios;
复制代码
分类:
前端
  •