Axios源码解读

112 阅读7分钟

定义

  • Axios 是一个基于promise 的HTTP 库,可以用在浏览器和node.js 中

特性

  • 基于 Promise
  • 支持浏览器和 node.js环境
  • 可添加请求、响应拦截器和转换请求和响应数据
  • 请求可以取消、中断
  • 自动转换 JSON 数据
  • 客户端支持防范 XSRF

执行流程

image.png

核心功能

  • 目录结构

image.png

  • 入口
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);
  // 创建实例 bind后返回的是一个函数,并且上下文指向context
  var instance = bind(Axios.prototype.request, context);
  // 拷贝prototype到实例上 类似于把Axios的原型上的方法(例如: request、get、post...)继承到实例上,this指向为context
  utils.extend(instance, Axios.prototype, context);
  // 拷贝上下文对象属性(默认配置和请求、相应拦截器对象)到实例上, this指向为context
  utils.extend(instance, context);
  
  // 创建axios实例,一般axios封装 应该都会用到 (我们把一些默认、公共的配置都放到一个实例上,复用实例,无需每次都重新创建实例)
  instance.create = function create(instanceConfig) {
    // 这里mergeConfig 就是用来深度合并的
    return createInstance(mergeConfig(defaultConfig, instanceConfig));
  };

	// 返回实例
  return instance;
}

// 创建实例 defaulst为默认配置 
var axios = createInstance(defaults);

// 向外暴露Axios类,可用于继承 
axios.Axios = Axios;

// 这里抛出 中断/取消请求的相关方法到入口对象
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');

// 并发请求 完全就是用promise的能力
axios.all = function all(promises) {
  return Promise.all(promises);
};
// 和axios.all 共同使用,单个形参数组参数转为多参 
axios.spread = require('./helpers/spread');

// 用作监测是否为Axios抛出的错误
axios.isAxiosError = require('./helpers/isAxiosError');
// 导出
module.exports = axios;

// 允许在ts中使用默认导出
module.exports.default = axios;


createInstance这个函数大概做了如下几件事

  • 首先是根据默认配置构建个上下文对象,包括默认配置和请求、相应拦截器对象
  • 创建实例 bind后返回的是一个函数, 所以我们使用时可以 axios(config)  这么使用,并且指向当前上下文
  • 把Axios的原型上的方法(例如: request、get、post...)继承到实例上,使用时才可以 axios.get()、axios.post()   ,this指向为context
  • 拷贝上下文对象属性(默认配置和请求、相应拦截器对象)到实例上, this指向为context
  • 返回实例(实例为函数)

核心请求方法

Axios.prototype.request = function request(config) {
    // 请求拦截器储存数组
    var requestInterceptorChain = [];

    // 默认所有请求拦截器都为同步
    var synchronousRequestInterceptors = true;
    this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {

        // 这里interceptor是注册的每一个拦截器对象 axios请求拦截器向外暴露了runWhen配置来针对一些需要运行时检测来执行的拦截器
        // 如果配置了该函数,并且返回结果为true,则记录到拦截器链中,反之则直接结束该层循环
        if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
            return;
        }

        // interceptor.synchronous 是对外提供的配置,可标识该拦截器是异步还是同步 默认为false(异步) 
        // 这里是来同步整个执行链的执行方式的,如果有一个请求拦截器为异步 那么下面的promise执行链则会有不同的执行方式
        synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;

        // 塞到请求拦截器数组中
        requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
    });

    // 相应拦截器存储数组
    var responseInterceptorChain = [];

    // 遍历按序push到拦截器存储数组中
    this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
        responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
    });

    var promise;

    // 如果为异步 其实也是默认情况
    if (!synchronousRequestInterceptors) {
        var chain = [dispatchRequest, undefined];

        // 请求拦截器塞到前面
        Array.prototype.unshift.apply(chain, requestInterceptorChain);

        // 响应拦截器塞到后面
        chain = chain.concat(responseInterceptorChain);
        promise = Promise.resolve(config);

        // 循环 执行
        while (chain.length) {
            promise = promise.then(chain.shift(), chain.shift());
        }
        return promise;
    }

    // 这里则是同步的逻辑 
    var newConfig = config;

    // 请求拦截器一个一个的走 返回 请求前最新的config
    while (requestInterceptorChain.length) {
        var onFulfilled = requestInterceptorChain.shift();
        var onRejected = requestInterceptorChain.shift();

        // 做异常捕获 有错直接抛出
        try {
            newConfig = onFulfilled(newConfig);
        } catch (error) {
            onRejected(error);
            break;
        }
    }

    // 到这里 微任务不会过早的创建 也就解决了 微任务过早创建、当前宏任务过长或某个请求拦截器中有异步任务而阻塞真正的请求延时发起问题
    try {
        promise = dispatchRequest(newConfig);
    } catch (error) {
        return Promise.reject(error);
    }

    // 响应拦截器执行
    while (responseInterceptorChain.length) {
        promise = promise.then(responseInterceptorChain.shift(), responseInterceptorChain.shift());
    }

    return promise;
};

拦截器实现

实际使用axios中,请求、相应拦截器是经常使用的,这也是axios的特点之一。拦截器是何时创建的呢,我们在axios.createcreateInstancenew Axios实例时构构建出来的

function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  这里创建的请求和响应拦截器 通过统一的类构造出来的
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

/core/InterceptorManager.js中:

function InterceptorManager() {
  this.handlers = [];
}
// 添加拦截器 添加成功、失败回调
InterceptorManager.prototype.use = function use(fulfilled, rejected, options) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected,
    synchronous: options ? options.synchronous : false,
    runWhen: options ? options.runWhen : null
  });
  return this.handlers.length - 1;
};

// 注销指定拦截器
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) {
    // 确定没被eject注销 才执行
    if (h !== null) {
      fn(h);
    }
  });
};

module.exports = InterceptorManager;

拦截器的实现是通过统一模型,构造统一控制器管理拦截器的注册、注销、执行。

适配器adapter

function getDefaultAdapter() {
  var adapter;
  // 判断XMLHttpRequest对象是否存在 存在则代表为浏览器环境
  if (typeof XMLHttpRequest !== 'undefined') {
    // For browsers use XHR adapter
    adapter = require('./adapters/xhr');
    // node环境 使用原生http发起请求
  } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
    adapter = require('./adapters/http');
  }
  return adapter;
}

./adapters/xhr.js 则是对原生ajax XMLHttpRequest对象的的封装,./adapters/http.js 则是对node http模块的封装,也会针对https做相应处理。

axios主动取消请求

使用方式

import { CancelToken } from axios;

// source为一个对象 结构为 { token, cancel }
// token用来表示某个请求,是个promise
// cancel是一个函数,当被调用时,则取消token注入的那个请求
const source = CancelToken.source();

axios
    .get('/user', {
        // 将token注入此次请求
        cancelToken: source.token, 
    })
    .catch(function (thrown) {
        // 判断是否是因为主动取消而导致的
        if (axios.isCancel(thrown)) {
            console.log('主动取消', thrown.message);
        } else {
            console.error(thrown);
        }
    });
// 这里调用cancel方法,则会中断该请求 无论请求是否成功返回
source.cancel('我主动取消请求')

lib/axios.js axios实例对外抛出了三个取消请求的相关接口,我们来看一下涉及取消请求的是三个文件,在 /lib/cancel/ 中 , 分别的作用:

  • Cancel.js : Cancel函数(伪造类),接受参数message其实就是调用source.cancel()中的参数:取消信息,,原型对象上的__CANCEL__ 属性,是为了标识该请求返回信息是否为取消请求返回的信息

  • CancelToken.jsCancelToken提供创建token实例注册取消请求能力及提供取消请求方法

  • isCancel.js :用于判断是为为取消请求返回的结果,也就是是否是Cancel实例

主要看下cancelToken的实现

function CancelToken(executor) {
  // 类型判断
  if (typeof executor !== 'function') {
    throw new TypeError('executor must be a function.');
  }
  // 创建一个promise的实例
  var resolvePromise;
  this.promise = new Promise(function promiseExecutor(resolve) {
  // 把resolve方法提出来 当resolvePromise执行时,this.promise状态会变为fulfilled
    resolvePromise = resolve;
  });
  // 存一下this
  var token = this;
  // new CancelToken时会立即调用executor方法 也就是 会执行source方法中的cancel = c;
  // 这里也就是把cancel函数暴露出去了,把取消的时机留给了使用者 使用者调用cancel时候也就会执行函数内的逻辑
  executor(function cancel(message) {
    // 请求已经被取消了直接return
    if (token.reason) {
      return;
    }
		// 给token(可就是当前this上)添加参数 调用new Cancel构造出cancel信息实例
    token.reason = new Cancel(message);
    // 这里当主动调用cancel方法时,就会把this.promise实例状态改为fulfilled,resolve出的信息则是reason(new Cancel实例)
    resolvePromise(token.reason);
  });
}

CancelToken中 会创建一个promise实例,和一个reason存储取消信息,当使用者调用source.cancel(message)方法时,会将该promise实例状态改为fulfilled,同时根据参数message创建reason错误信息实例,实例上还有__CANCEL__属性,标识他是取消请求返回的信息

调用取消后的执行流程

image.png

其他

  • 合并配置 merge函数:递归的去合并, 用在合并一些请求config配置信息,实现方法其实和递归实现深拷贝deepClone类似
function merge(/* obj1, obj2, obj3, ... */) {
  var result = {};
  // 闭包处理逻辑函数
  function assignValue(val, key) {
    // result里有该键值并且 同为普通Object对象类型递归merge
    if (isPlainObject(result[key]) && isPlainObject(val)) {
      result[key] = merge(result[key], val);
      // result里没有 赋值
    } else if (isPlainObject(val)) {
      result[key] = merge({}, val);
      // 数组类型
    } else if (isArray(val)) {
      result[key] = val.slice();
      // 其他类型直接赋值
    } else {
      result[key] = val;
    }
  }
	// 循环入参调用
  for (var i = 0, l = arguments.length; i < l; i++) {
    forEach(arguments[i], assignValue);
  }
  // 返回合并后的结果
  return result;
}

总结:Axios的核心功能分析如上,细节可自行查看源码