axios
的源码相对还是比较简单的,但是中间件的思想还是很巧妙的,读的过程中做了一些中文注释。
在使用 axios
时,都会使用 axios.create
方法去创建一个实例,那么就找到 create
方法,从他看起。首先找到入口文件 lib/axios.js
。
// 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 the default instance to be exported
var axios = createInstance(defaults)
// Expose Axios class to allow class inheritance
axios.Axios = Axios
// 使用 create 创建实例
axios.create = function create(instanceConfig) {
// 使用 createInstance 创建实例时,会将传入的 config 挂载到实例的 defaults 上
return createInstance(mergeConfig(axios.defaults, instanceConfig))
}
// 扩展 axios
// 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')
// 导出 axios
module.exports = axios
// Allow use of default import syntax in TypeScript
module.exports.default = axios
入口文件导出了 axios
,并且定义了 create
方法。create
方法内部返回了 createInstance
方法的调用,反观上边定义的 createInstance
方法。
// lib/axios.js
/**
* 创建一个 axios 实例
*
* @param {Object} defaultConfig 实例的默认 config
* @return {Axios} axios 实例
*/
function createInstance(defaultConfig) {
// 使用 Axios 构造函数生成实例,上边包括了 defaults 和 interceptors 属性
var context = new Axios(defaultConfig)
// bind 返回一个方法,使用 context 调用 Axios.prototype.request
var instance = bind(Axios.prototype.request, context)
// instance 继承 Axios.prototype 的属性,如果是方法将 this 指向 context
utils.extend(instance, Axios.prototype, context)
// instance 继承 context 的属性
utils.extend(instance, context)
// 返回 instance,执行时实际执行的是 Axios.prototype.request
return instance
}
利用 new Axios(defaultConfig)
生成 axios
实例,以下是 Axios
构造函数。
// lib/core/Axios.js
var InterceptorManager = require('./InterceptorManager')
/**
* Axios 的构造函数
*
* @param {Object} instanceConfig 生成实例所需的 config
*/
function Axios(instanceConfig) {
// 将 instanceConfig 赋值到实例的 defaults 上
this.defaults = instanceConfig
// 实例上定义拦截器,分别为 请求拦截器 和 响应拦截器
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager(),
}
}
module.exports = Axios
然后先来看看 InterceptorManager
是个什么东西。
// lib/core/InterceptorManager.js
var utils = require('./../utils')
function InterceptorManager() {
// 实例上添加 handlers 数组,方便后续的循环操作
this.handlers = []
}
/**
* 添加一个新的拦截器到 handles 中
*
* @param {Function} fulfilled The function to handle `then` for a `Promise`
* @param {Function} rejected The function to handle `reject` for a `Promise`
*
* @return {Number} 返回一个 id 用来删除拦截器
*/
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected,
})
return this.handlers.length - 1
}
/**
* 从 handles 中删除一个指定的拦截器
*
* @param {Number} id use 中返回的 id
*/
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null
}
}
/**
* 迭代拦截器
*
* @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
想必 use
方法大家都很熟悉,在定义请求拦截器和响应拦截器时都会用到,作用就是向对应的拦截器实例的 handlers
上 push 成功和失败的回调。然后在调用 Axios.prototype.request
的时候最这些回调进行处理。
// lib/core/Axios.js
var utils = require('./../utils')
var buildURL = require('../helpers/buildURL')
var dispatchRequest = require('./dispatchRequest')
var mergeConfig = require('./mergeConfig')
/**
* 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
// 因为 shift 两次,所以用 undefined 占位
var chain = [dispatchRequest, undefined]
// 返回 config
var promise = Promise.resolve(config)
// 请求拦截器添加到 chain 的前边
this.interceptors.request.forEach(function unshiftRequestInterceptors(
interceptor
) {
// 成功和失败依次添加
chain.unshift(interceptor.fulfilled, interceptor.rejected)
})
// 响应拦截器添加到 chain 的后边
this.interceptors.response.forEach(function pushResponseInterceptors(
interceptor
) {
// 成功和失败依次添加
chain.push(interceptor.fulfilled, interceptor.rejected)
})
while (chain.length) {
// 第一次 shift 拿到 fulfilled,第二次 shift 拿到 rejected
// Promise.resolve(config),第一次的请求拦截器可以拿到 config
promise = promise.then(chain.shift(), chain.shift())
}
return promise
}
这个方法中一开始对 config
进行了一些处理,然后定义了一个 chain
数组,接下来就是中间件的核心代码了。chain
默认值是实际发请求的代码,然后将 config
使用 Promise.resolve
包装成 Promise 对象,接下来遍历 request
这个请求拦截器实例,将成功和失败的回调添加到 chain
的前边;响应拦截器实例同理,只不过要放到 chain
的后边。
这样以来,chain
的长度不为零时,循环执行 then 方法,就会先去执行所有定义好的请求拦截器,因为用到了 unshift
去添加,所以最先定义的会最后执行,然后执行 dispatchRequest
,最后再执行所有定义好的响应拦截器,这就是中间件的整个流程。
再来看看 dispatchRequest
中做了什么,他是怎么做到浏览器和 node 中都可以使用的。
// lib/core/dispatchRequest.js
/**
* 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) {
var adapter = config.adapter || defaults.adapter
// 调用 adapter 返回浏览器或 node 的请求方法
return adapter(config).then(
function onAdapterResolution(response) {
// Transform response data
response.data = transformData(
response.data,
response.headers,
config.transformResponse
)
return response
},
function onAdapterRejection(reason) {
if (!isCancel(reason)) {
// Transform response data
if (reason && reason.response) {
reason.response.data = transformData(
reason.response.data,
reason.response.headers,
config.transformResponse
)
}
}
return Promise.reject(reason)
}
)
}
这个方法主要用到了 adapter
,用户如果不自定义,会用 axios
提供的默认值,方法中对成功和失败的数据进行了处理并返回 Promise ,看样子 adapter
就是发请求的方法了。
// lib/default.js
// 返回浏览器或 node 的请求方式
function getDefaultAdapter() {
var adapter
if (typeof XMLHttpRequest !== 'undefined') {
// For browsers use XHR adapter
adapter = require('./adapters/xhr')
} else if (
typeof process !== 'undefined' &&
Object.prototype.toString.call(process) === '[object process]'
) {
// For node use HTTP adapter
adapter = require('./adapters/http')
}
return adapter
}
// 这个 defaults 就是入口文件导出的 axios 实例化时传入的 config
var defaults = {
adapter: getDefaultAdapter(),
...
}
发请求时会判断当前是什么环境,返回对应的网络请求方法。感兴趣的小伙伴可以自行阅读这两个文件:
以上就是读 axios
源码的全过程。