axios 是一个基于 Promise 的 HTTP 工具库,可以运行在浏览器和 Node.js 上。代码量少,读起来不会花费太多时间。
初次阅读,不用面面俱到,重点是理解主要流程。为此,本文摘抄的 axios 源码经过大幅删减,只保留主干逻辑。
首先认识一下 axios 的主要目录及其作用。
目录 | 说明 |
---|---|
lib/ | 源码根目录 |
lib/adapters | 跨平台的适配器 |
lib/cancel | 定义 CancelToken |
lib/core | 核心领域模块 |
lib/helpers | 与核心领域无关的辅助工具 |
dist/ | 编译输出目录 |
test/ | 测试用例 |
下一步是找到入口文件。从 package.json 的 build 命令开始,顺藤摸瓜,最终在 webpack.config.js 中定位到入口文件:index.js
然而 index.js 只有一行代码:
module.exports = require('./lib/axios');
用户经常使用的 api 函数,统统在 lib/axios.js 中定义。
// Axios 定义构造函数var Axios = require('./core/Axios');// defaults 定义 config 的默认值var defaults = require('./defaults');function createInstance(defaultConfig) { ... }var axios = createInstance(defaults);axios.Axios = Axios;axios.create = function(instanceConfig) { ... }// 定义了取消请求的三个对象axios.Cancel = require('./cancel/Cancel');axios.CancelToken = require('./cancel/CancelToken');axios.isCancel = require('./cancel/isCancel');module.exports = axios;
axios 是 Axios 类型的一个实例,它的变量和行为在 lib/core/Axios.js 中定义如下:
var InterceptorManager = require('./InterceptorManager');function Axios(instanceConfig) { this.defaults = instanceConfig; this.interceptors = { request: new InterceptorManager(), response: new InterceptorManager(), }}// 每次请求都会执行一次 request() 方法Axios.prototype.request = function(config) { ... }module.exports = Axios;
在 Axios 构造函数中,为每个实例创建 defaults 和 interceptors 属性。前者储存实例级别的 config 参数,后者是空的拦截器。拦截器下又有两个成员,分别对应请求拦截器和响应拦截器。
我们来看看拦截器是如何实现的(lib/core/InterceptorManager.js)。
// 构造函数,定义拦截器数组 handlersfunction InterceptorManager() { this.handlers = [];}// 每次执行 use() 都会向拦截器数组增加两个处理函数// 前者处理 then() 回调,后者处理 catch() 回调InterceptorManager.prototype.use = function(f, r) { this.handlers.push({ fulfilled: f, rejected: r, }); return this.handlers.length - 1;}// 遍历拦截器数组,每个元素执行一次 fnInterceptorManager.prototype.forEach = function(fn) { forEach(this.handlers, function(h) { if (h !== null) { fn(h); } });}
拦截器的实现相对简单,只是定义了一个数组,暴露了 use() 和 forEach() 两个方法。
回到 Axios,看看最核心的函数 request() 方法会进行什么操作。
var dispatchRequest = require('./dispatchRequest');Axios.prototype.request = function(config) { var chain = [dispatchRequest, undefined]; var promise = Promise.resolve(config); this.interceptors.request.forEach((interceptor) => { chain.unshift(interceptor.fulfilled, interceptor.rejected); }); this.interceptors.response.forEach((interceptor) => { chain.push(interceptor.fulfilled, interceptor.rejected); }); while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); } return promise;}
从上面代码可以看出,request() 方法会生成一条长长的 promise 调用链。链的头部是请求拦截器,中部为真正的请求动作(dispatchRequest),尾部是响应拦截器。
真正的请求动作(lib/core/dispatchRequest.js)定义了统一的响应处理逻辑。这些处理逻辑相对高层,与底层的环境无关。主要逻辑如下:
module.exports = function dispatchRequest(config) { // 如果取消请求,则抛出异常,后面的 promise 直接走 catch() 分支 throwIfCancellationRequested(config); var adapter = config.adapter || defaults.adapter; return adapter(config).then((response) => { throwIfCancellationRequested(config); response.data = transformData(...); return response; }, (reason) => { return Promise.reject(reason); });}
axios 在浏览器使用 XMLHttpRequest 发送 ajax 请求,在 Node.js 使用 http 模块。这些和运行环境相关的平台差异,被 axios 使用 adapter(适配器) 抹平了。
默认情况下,会使用 lib/defaults.js 定义的默认适配器。默认适配器会根据平台的特征差异,自动选择对应的适配器。具体逻辑如下:
function getDefaultAdapter() { var adapter; if (typeof XMLHttpRequest !== 'undefined') { adapter = require('./adapter/xhr'); } else if (typeof process !== 'undefined') { adapter = require('./adapter/http'); } return adapter;}var defaults = { adapter: getDefaultAdapter(),};module.exports = defaults;
目前 axios 的适配器有两个,分别是 lib/adapters/xhr.js 和 lib/adapters/http.js,对应着浏览器环境和 node.js 环境。它们的作用类似,都是把原生的 callback 类型回调,封装为 Promise 异步风格,方便链式调用。
如果要向其他平台移植 axios(比如各种各样的小程序平台),只需为不同平台增加对应的适配器即可。
(完)