共计 11410 个字符,预计需要花费 29 分钟才能阅读完成。
前言
这应该是一个大多数都常用的请求库, 因为它可以支持多种配置, 跨平台实现, 返回 promise 进行链式调用. 完全过一遍源码可以提升自己对请求库的理解知识
axios 源码系列(一) — 目录结构和工具函数
axios 源码系列(二) — 适配器内部
axios 源码系列(三) — 默认配置和取消请求
axios 源码系列(四) — Axios 和 dispatchRequest 与拦截器
axios 创建
这就是 Axios 库暴露出来的使用方法
axios/lib/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');
/**
* Create an instance of Axios
*
* @param {Object} defaultConfig The default config for the instance
* @return {Axios} A new instance of Axios
*/
function createInstance(defaultConfig) {var context = new Axios(defaultConfig);
var instance = bind(Axios.prototype.request, context);
// Copy axios.prototype to instance
utils.extend(instance, Axios.prototype, context);
// Copy context to instance
utils.extend(instance, context);
return instance;
}
// Create the default instance to be exported
var axios = createInstance(defaults);
axios 实例实际上就是由 Axios 构造函数创建出来, 再将原型上的 request
的 this 指向 axios 实例, 然后调用 utils.extend
扩展函数将 axios.prototype
和context
附加在 axios 实例上,
上面可能有疑问的点在于这两点, 好像显得多此一举
// Copy axios.prototype to instance
utils.extend(instance, Axios.prototype, context);
// Copy context to instance
utils.extend(instance, context);
但是在这里的 extend
里不单单是做扩展, 还额外增加了绑定 this 指向的功能, 所以在两次操作实现目的不一样
-
instance
是被扩展对象,Axiox.prototype
是用于扩展的对象,context
是作为扩展对象相关function
的this
指定对象 -
instance
依然是被扩展对象, 但这次context
是用于扩展的对象
// Expose Axios class to allow class inheritance
axios.Axios = Axios;
// Factory for creating new instances
axios.create = function create(instanceConfig) {return createInstance(mergeConfig(axios.defaults, instanceConfig));
};
我们看看内部是怎么合并配置的
axios/lib/core/mergeConfig.js
这是负责合并配置的方法
var utils = require('../utils');
/**
* Config-specific merge-function which creates a new config-object
* by merging two configuration objects together.
*
* @param {Object} config1
* @param {Object} config2
* @returns {Object} New object resulting from merging config2 to config1
*/
module.exports = function mergeConfig(config1, config2) {
// eslint-disable-next-line no-param-reassign
config2 = config2 || {};
var config = {};
var valueFromConfig2Keys = ['url', 'method', 'params', 'data'];
var mergeDeepPropertiesKeys = ['headers', 'auth', 'proxy'];
var defaultToConfig2Keys = [
'baseURL', 'url', 'transformRequest', 'transformResponse', 'paramsSerializer',
'timeout', 'withCredentials', 'adapter', 'responseType', 'xsrfCookieName',
'xsrfHeaderName', 'onUploadProgress', 'onDownloadProgress',
'maxContentLength', 'validateStatus', 'maxRedirects', 'httpAgent',
'httpsAgent', 'cancelToken', 'socketPath'
];
utils.forEach(valueFromConfig2Keys, function valueFromConfig2(prop) {if (typeof config2[prop] !== 'undefined') {config[prop] = config2[prop];
}
});
utils.forEach(mergeDeepPropertiesKeys, function mergeDeepProperties(prop) {if (utils.isObject(config2[prop])) {config[prop] = utils.deepMerge(config1[prop], config2[prop]);
} else if (typeof config2[prop] !== 'undefined') {config[prop] = config2[prop];
} else if (utils.isObject(config1[prop])) {config[prop] = utils.deepMerge(config1[prop]);
} else if (typeof config1[prop] !== 'undefined') {config[prop] = config1[prop];
}
});
utils.forEach(defaultToConfig2Keys, function defaultToConfig2(prop) {if (typeof config2[prop] !== 'undefined') {config[prop] = config2[prop];
} else if (typeof config1[prop] !== 'undefined') {config[prop] = config1[prop];
}
});
var axiosKeys = valueFromConfig2Keys
.concat(mergeDeepPropertiesKeys)
.concat(defaultToConfig2Keys);
var otherKeys = Object
.keys(config2)
.filter(function filterAxiosKeys(key) {return axiosKeys.indexOf(key) === -1;
});
utils.forEach(otherKeys, function otherKeysDefaultToConfig2(prop) {if (typeof config2[prop] !== 'undefined') {config[prop] = config2[prop];
} else if (typeof config1[prop] !== 'undefined') {config[prop] = config1[prop];
}
});
return config;
};
从调用的方式可得,config1 是 axios 的默认配置,config2 是用户自定义的配置
里面默认有三种不同优先级的配置
- valueFromConfig2Keys: 指定只要 config2 内的相关属性不为 undefined 则取 config2 的值, 这里根本不考虑 config1 配置
- mergeDeepPropertiesKeys: 深层请求头结构,config2 优先级高于 config1, 先处理对象结构再处理其他非 undefined 类型
- defaultToConfig2Keys: 可直接赋值的请求头列表,config2 优先级高于 config1
遍历处理得出上面三种配置之后, 我们可以得到
- axiosKeys: config1 和 config2 含有上面三种默认配置合并后得到的映射数组
- otherKeys: 过滤出 config2 中
axiosKeys
里不包含的配置属性, 基本是非常用或者自定义的字段, 同样不考虑 config1 的情况
最后就是将 otherKeys 的相关配置也合并到最终结果并返回该结果
// Expose Cancel & CancelToken
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');
// Expose all/spread
axios.all = function all(promises) {return Promise.all(promises);
};
axios.spread = require('./helpers/spread');
module.exports = axios;
// Allow use of default import syntax in TypeScript
module.exports.default = axios;
在 axios 实例上添加方法, 我们先看 spread
是有什么作用
axios/lib/helpers/spread.js
/**
* Syntactic sugar for invoking a function and expanding an array for arguments.
*
* Common use case would be to use `Function.prototype.apply`.
*
* ```js
* function f(x, y, z) {}
* var args = [1, 2, 3];
* f.apply(null, args);
* ```
*
* With `spread` this example can be re-written.
*
* ```js
* spread(function(x, y, z) {})([1, 2, 3]);
* ```
*
* @param {Function} callback
* @returns {Function}
*/
module.exports = function spread(callback) {return function wrap(arr) {return callback.apply(null, arr);
};
};
只是一句语法糖, 没有黑魔法
Axios 核心构造函数
axios/lib/core/Axios.js
这是 Axios 的构造函数
var utils = require('./../utils');
var buildURL = require('../helpers/buildURL');
var InterceptorManager = require('./InterceptorManager');
var dispatchRequest = require('./dispatchRequest');
var mergeConfig = require('./mergeConfig');
/**
* Create a new instance of Axios
*
* @param {Object} instanceConfig The default config for the instance
*/
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {request: new InterceptorManager(),
response: new InterceptorManager()};
}
创建 Axios 实例默认会保存配置, 并且内置拦截器分别给请求和响应
/**
* Dispatch a request
*
* @param {Object} config The config specific for this request (merged with this.defaults)
*/
Axios.prototype.request = function request(config) {
/*eslint no-param-reassign:0*/
// Allow for axios('example/url'[, config]) a la fetch API
if (typeof config === 'string') {config = arguments[1] || {};
config.url = arguments[0];
} else {config = config || {};
}
config = mergeConfig(this.defaults, config);
// Set config.method
if (config.method) {config.method = config.method.toLowerCase();
} else if (this.defaults.method) {config.method = this.defaults.method.toLowerCase();
} else {config.method = 'get';}
// Hook up interceptors middleware
var chain = [dispatchRequest, undefined];
var promise = Promise.resolve(config);
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);
});
while (chain.length) {promise = promise.then(chain.shift(), chain.shift());
}
return promise;
};
request 分两部分逻辑
- 支持首个参数传入请求地址的调用方式, 合并默认配置和传入配置
-
chain
作为钩子中间件进行链式调用, 最终变成[ this.interceptors.request.fulfilled, this.interceptors.request.rejected,// 请求拦截 dispatchRequest, undefined,// 发起请求 this.interceptors.response.fulfilled, this.interceptors.response.rejected,// 响应拦截 ]
- 使用
while
链式调用chain
的函数, 完成从发起到接收请求的完整流程
Axios.prototype.getUri = function getUri(config) {
// 合并配置
config = mergeConfig(this.defaults, config);
// 返回拼装 URL
return buildURL(config.url, config.params, config.paramsSerializer).replace(/^\?/, '');
};
// Provide aliases for supported request methods
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function(url, config) {return this.request(utils.merge(config || {}, {
method: method,
url: url
}));
};
});
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function(url, data, config) {return this.request(utils.merge(config || {}, {
method: method,
url: url,
data: data
}));
};
});
module.exports = Axios;
遍历所有请求方式调用内置的 request 方法
拦截器
axios/lib/core/InterceptorManager.js
定义拦截器的构造函数
var utils = require('./../utils');
function InterceptorManager() {this.handlers = [];
}
/**
* Add a new interceptor to the stack
*
* @param {Function} fulfilled The function to handle `then` for a `Promise`
* @param {Function} rejected The function to handle `reject` for a `Promise`
*
* @return {Number} An ID used to remove interceptor later
*/
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
return this.handlers.length - 1;
};
/**
* Remove an interceptor from the stack
*
* @param {Number} id The ID that was returned by `use`
*/
InterceptorManager.prototype.eject = function eject(id) {if (this.handlers[id]) {this.handlers[id] = null;
}
};
/**
* Iterate over all the registered interceptors
*
* This method is particularly useful for skipping over any
* interceptors that may have become `null` calling `eject`.
*
* @param {Function} fn The function to call for each interceptor
*/
InterceptorManager.prototype.forEach = function forEach(fn) {utils.forEach(this.handlers, function forEachHandler(h) {if (h !== null) {fn(h);
}
});
};
module.exports = InterceptorManager;
整个函数其实只做了几件事:
- 生成实例, 内置一个
handlers
队列 - 原型绑定
use
注册方法, 队列传入包含满足和失败函数方法的拦截对象, 返回该对象在队列中的 id(实际上就是索引值) - 原型绑定
eject
解绑方法, 队列移除指定 id 的拦截对象 - 原型绑定
forEach
遍历方法, 迭代所有已注册的拦截器
拦截器示例
// 添加请求拦截器
const reqInterceptor = axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
const resInterceptor = axios.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
// 移除
axios.interceptors.request.eject(reqInterceptor);
axios.interceptors.request.eject(resInterceptor);
调度请求
axios/lib/core/dispatchRequest.js
这是负责调度请求的函数, 返回 Promise.
var utils = require('./../utils');
var transformData = require('./transformData');
var isCancel = require('../cancel/isCancel');
var defaults = require('../defaults');
/**
* Throws a `Cancel` if cancellation has been requested.
*/
function throwIfCancellationRequested(config) {if (config.cancelToken) {config.cancelToken.throwIfRequested();
}
}
/**
* Dispatch a request to the server using the configured adapter.
*
* @param {object} config The config that is to be used for the request
* @returns {Promise} The Promise to be fulfilled
*/
module.exports = function dispatchRequest(config) {throwIfCancellationRequested(config);
// Ensure headers exist
config.headers = config.headers || {};
// Transform request data
config.data = transformData(
config.data,
config.headers,
config.transformRequest
);
// Flatten headers
config.headers = utils.merge(config.headers.common || {},
config.headers[config.method] || {},
config.headers || {});
utils.forEach(['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
function cleanHeaderConfig(method) {delete config.headers[method];
}
);
var adapter = config.adapter || defaults.adapter;
return adapter(config).then(function onAdapterResolution(response) {throwIfCancellationRequested(config);
// Transform response data
response.data = transformData(
response.data,
response.headers,
config.transformResponse
);
return response;
}, function onAdapterRejection(reason) {if (!isCancel(reason)) {throwIfCancellationRequested(config);
// Transform response data
if (reason && reason.response) {
reason.response.data = transformData(
reason.response.data,
reason.response.headers,
config.transformResponse
);
}
}
return Promise.reject(reason);
});
};
throwIfCancellationRequested
是如果已经取消请求则抛出错误
dispatchRequest
使用配置的 adapter
向服务端发起请求, 里面分几个步骤:
- 检测到如果请求已经取消则抛出错误
- 确保请求头存在
- 传入数据和请求头, 根据数据类型转换请求数据
- 抽出请求头的
common
和当前请求的methods
合并到首层结构 - 删除 header 属性里无用的属性
- 确认
adapter
-
执行
adapter(config)
返回 Promise 然后执行对应操作-
成功
- 检测到如果请求已经取消则抛出错误
- 否则处理返回数据格式
-
失败
- 如果
!isCancel(reason)
成立, 检测到如果请求已经取消则抛出错误 - 否则直接返回数据
- 如果
-
axios/lib/core/transformData.js
负责解析请求 / 响应数据, 只是单纯的使用 utils.forEach
而已
var utils = require('./../utils');
/**
* Transform the data for a request or a response
*
* @param {Object|String} data The data to be transformed
* @param {Array} headers The headers for the request or response
* @param {Array|Function} fns A single function or Array of functions
* @returns {*} The resulting transformed data
*/
module.exports = function transformData(data, headers, fns) {
/*eslint no-param-reassign:0*/
utils.forEach(fns, function transform(fn) {data = fn(data, headers);
});
return data;
};
至此, 整个 axios 库的源码已经讲完了.