前言
这应该是一个大多数都常用的请求库,因为它可以支持多种配置,跨平台实现,返回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 exportedvar axios = createInstance(defaults);
axios实例实际上就是由Axios构造函数创建出来,再将原型上的request
的this指向axios实例,然后调用utils.extend
扩展函数将axios.prototype
和context
附加在axios实例上,
上面可能有疑问的点在于这两点,好像显得多此一举
// Copy axios.prototype to instanceutils.extend(instance, Axios.prototype, context);// Copy context to instanceutils.extend(instance, context);
但是在这里的extend
里不单单是做扩展,还额外增加了绑定this指向的功能,所以在两次操作实现目的不一样
instance
是被扩展对象,Axiox.prototype
是用于扩展的对象,context
是作为扩展对象相关function
的this
指定对象instance
依然是被扩展对象,但这次context
是用于扩展的对象
// Expose Axios class to allow class inheritanceaxios.Axios = Axios;// Factory for creating new instancesaxios.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 & CancelTokenaxios.Cancel = require('./cancel/Cancel');axios.CancelToken = require('./cancel/CancelToken');axios.isCancel = require('./cancel/isCancel');// Expose all/spreadaxios.all = function all(promises) { return Promise.all(promises);};axios.spread = require('./helpers/spread');module.exports = axios;// Allow use of default import syntax in TypeScriptmodule.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 methodsutils.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库的源码已经讲完了.