这应该是一个大多数都经常使用的请求库,由于它能够支持多种配置,跨平台实现,返回promise进行链式调用.彻底过一遍源码能够提高本身对请求库的理解知识ios
axios源码系列(一) --- 目录结构和工具函数
axios源码系列(二) --- 适配器内部
axios源码系列(三) --- 默认配置和取消请求
axios源码系列(四) --- Axios和dispatchRequest与拦截器axios
这就是Axios库暴露出来的使用方法segmentfault
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实例上,数组
上面可能有疑问的点在于这两点,好像显得画蛇添足promise
// Copy axios.prototype to instance utils.extend(instance, Axios.prototype, context); // Copy context to instance utils.extend(instance, context);
可是在这里的extend
里不仅仅是作扩展,还额外增长了绑定this指向的功能,因此在两次操做实现目的不同app
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)); };
咱们看看内部是怎么合并配置的socket
这是负责合并配置的方法ide
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是用户自定义的配置函数
里面默认有三种不一样优先级的配置工具
遍历处理得出上面三种配置以后,咱们能够获得
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
是有什么做用
/** * 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的构造函数
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方法
定义拦截器的构造函数
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);
这是负责调度请求的函数,返回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
合并到首层结构adapter
执行adapter(config)
返回Promise而后执行对应操做
成功
失败
!isCancel(reason)
成立,检测到若是请求已经取消则抛出错误负责解析请求/响应数据,只是单纯的使用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库的源码已经讲完了.