axios究竟有什么牛的呀?
对于前端开发者来说,axios可谓是无人不知无人不晓的最热门的请求库之一了。在axios的官方文档中我们可以知晓它的一些过人之处,如:兼容浏览器、Node端,拦截器,取消请求等。下面我们就从源码的角度去探索一下axios究竟有什么牛的吧。
在阅读源码之前,我们一起来思考一些对axios的主要疑惑?
- axios执行的基本流程是什么?
- axios如何实现adapter?
- axios如何实现cancellation?
- axios如何实现processCapture?
- axios如何实现Intercepters?
本文从以上5个问题来探求axios的源码实现。
1.axios执行的基本流程
基础类Axios
- 入口文件
首先我们先找到axios的入口:/lib/axios.js
,可以看到我们平时用到的axios其实是这样的:
/**
* Create an instance of Axios
*
* @param {Object} defaultConfig The default config for the instance
*
* @returns {Axios} A new instance of Axios
*/
function createInstance(defaultConfig) {
const context = new Axios(defaultConfig);
// 将最核心的request方法绑在实例上
const instance = bind(Axios.prototype.request, context);
// 将一些原型方法(get、request、post...)绑在实例上
utils.extend(instance, Axios.prototype, context, {allOwnKeys: true});
// 将一些属性(adapter、Intercepters...)绑在实例上
utils.extend(instance, context, null, {allOwnKeys: true});
// Factory for creating new instances
instance.create = function create(instanceConfig) {
return createInstance(mergeConfig(defaultConfig, instanceConfig));
};
return instance;
}
// Create the default instance to be exported
const axios = createInstance(defaults);
......
export default axios;
上述代码简而言之是将Axios原型的各种方法、属性绑定在了instance实例上。我们导出并使用的axios
就是一个实例。
- Axios类
在lib/core/Axios.js
中,最基础的Axios类就被编写在这里:
/**
* Create a new instance of Axios
*
* @param {Object} instanceConfig The default config for the instance
*
* @return {Axios} A new instance of Axios
*/
class Axios {
constructor(instanceConfig) {
// 默认配置
this.defaults = instanceConfig;
// 拦截器
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
/**
* Dispatch a request
*
* @param {String|Object} configOrUrl The config specific for this request (merged with this.defaults)
* @param {?Object} config
*
* @returns {Promise} The Promise to be fulfilled
*/
request(configOrUrl, config) {
/*eslint no-param-reassign:0*/
// 【核心】处理各种配置
// Allow for axios('example/url'[, config]) a la fetch API
if (typeof configOrUrl === 'string') {
config = config || {};
config.url = configOrUrl;
} else {
config = configOrUrl || {};
}
config = mergeConfig(this.defaults, config);
const {transitional, paramsSerializer, headers} = config;
// Set config.method
config.method = (config.method || this.defaults.method || 'get').toLowerCase();
let contextHeaders;
// Flatten headers
contextHeaders = headers && utils.merge(
headers.common,
headers[config.method]
);
// 将默认的Headers和用户自定义Headers合并
config.headers = AxiosHeaders.concat(contextHeaders, headers);
// 过滤掉跳过的拦截器
const requestInterceptorChain = [];
let synchronousRequestInterceptors = true;
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
return;
}
synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
});
const responseInterceptorChain = [];
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});
let promise;
let i = 0;
let len;
if (!synchronousRequestInterceptors) {
const chain = [dispatchRequest.bind(this), undefined];
chain.unshift.apply(chain, requestInterceptorChain);
chain.push.apply(chain, responseInterceptorChain);
len = chain.length;
promise = Promise.resolve(config);
while (i < len) {
promise = promise.then(chain[i++], chain[i++]);
}
return promise;
}
len = requestInterceptorChain.length;
let newConfig = config;
i = 0;
while (i < len) {
const onFulfilled = requestInterceptorChain[i++];
const onRejected = requestInterceptorChain[i++];
try {
newConfig = onFulfilled(newConfig);
} catch (error) {
onRejected.call(this, error);
break;
}
}
// 【核心】真正请求发送的地方~!
try {
promise = dispatchRequest.call(this, newConfig);
} catch (error) {
return Promise.reject(error);
}
i = 0;
len = responseInterceptorChain.length;
while (i < len) {
promise = promise.then(responseInterceptorChain[i++], responseInterceptorChain[i++]);
}
return promise;
}
// 生成最终的请求URL
getUri(config) {
config = mergeConfig(this.defaults, config);
const fullPath = buildFullPath(config.baseURL, config.url);
return buildURL(fullPath, config.params, config.paramsSerializer);
}
}
// 将一些get、post方法暴露出来
// 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(mergeConfig(config || {}, {
method,
url,
data: (config || {}).data
}));
};
});
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
function generateHTTPMethod(isForm) {
return function httpMethod(url, data, config) {
return this.request(mergeConfig(config || {}, {
method,
headers: isForm ? {
'Content-Type': 'multipart/form-data'
} : {},
url,
data
}));
};
}
Axios.prototype[method] = generateHTTPMethod();
Axios.prototype[method + 'Form'] = generateHTTPMethod(true);
});
我们可以关注到,整个Axios类只包括了两个属性和两个方法:
- 属性:
defaults
默认配置、interceptors
拦截器 - 方法:
request
最核心的请求方法、getUri
生成最终的请求URL
并且在最后将一些通用的get
、post
方法挂载在原型上。
了解完这些基础知识后,我们以axios.get('/user/info')
这个最简单的GET请求来追溯一下axios的执行流程。
1. 执行get方法
-
首先将参数传入最核心的
request
方法中 -
在
request
方法中,依次对url
、method
、transitional
,paramsSerializer
,headers
以及拦截器做配置处理,包括但不限于合并默认配置、过滤不必要的配置等操作。
这里的
transitional
是向后兼容配置,新版本将会移除。具体可见:github.com/axios/axios…
- 执行拦截器(这里先不赘述)
- 执行请求(dispatchRequest)
2. 发送请求
让我们来看一下dispatchRequest
方法里面到底藏着什么玄机。(路径为/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
*/
export default function dispatchRequest(config) {
throwIfCancellationRequested(config);
config.headers = AxiosHeaders.from(config.headers);
// Transform request data
config.data = transformData.call(
config,
config.transformRequest
);
if (['post', 'put', 'patch'].indexOf(config.method) !== -1) {
config.headers.setContentType('application/x-www-form-urlencoded', false);
}
// 【核心】适配器
const adapter = adapters.getAdapter(config.adapter || defaults.adapter);
return adapter(config).then(function onAdapterResolution(response) {
// 响应处理...
return response;
}, function onAdapterRejection(reason) {
// 错误处理...
return Promise.reject(reason);
});
}
dispatchRequest
的执行流程如下:
而适配器就是可以兼容浏览器和Nodejs环境进行发起网络请求的核心。如在浏览器中使用XMLHttpRequest
来进行请求,最后返回一个Promise。
这就是整个Axios的最简单的执行流程!
2.axios如何实现adapter
每一个前端人都应该背过面试题:Axios在Node环境下使用的是http
模块,而在浏览器环境下使用的是XMLHttpRequest
Api,但是大家都知道这层适配器是实现原理嘛?
而最关键的适配器的实现代码如下lib/adapter/adapter.js
:
const knownAdapters = {
http: httpAdapter,
xhr: xhrAdapter
}
utils.forEach(knownAdapters, (fn, value) => {
if(fn) {
try {
Object.defineProperty(fn, 'name', {value});
} catch (e) {
// eslint-disable-next-line no-empty
}
Object.defineProperty(fn, 'adapterName', {value});
}
});
export default {
getAdapter: (adapters) => {
adapters = utils.isArray(adapters) ? adapters : [adapters];
const {length} = adapters;
let nameOrAdapter;
let adapter;
for (let i = 0; i < length; i++) {
nameOrAdapter = adapters[i];
if((adapter = utils.isString(nameOrAdapter) ? knownAdapters[nameOrAdapter.toLowerCase()] : nameOrAdapter)) {
break;
}
}
if (!adapter) {
if (adapter === false) {
throw new AxiosError(
`Adapter ${nameOrAdapter} is not supported by the environment`,
'ERR_NOT_SUPPORT'
);
}
throw new Error(
utils.hasOwnProp(knownAdapters, nameOrAdapter) ?
`Adapter '${nameOrAdapter}' is not available in the build` :
`Unknown adapter '${nameOrAdapter}'`
);
}
if (!utils.isFunction(adapter)) {
throw new TypeError('adapter is not a function');
}
return adapter;
},
adapters: knownAdapters
}
首先让我们回到前一节所提到的入口代码:
const adapter = adapters.getAdapter(config.adapter || defaults.adapter);
其中,这里的defaults.adapter
值为['xhr', 'http']
。
然后通过判断knownAdapters
中的值来去获取相应的请求函数。
那么注意,在axios
判断处于那种环境的函数如下:
- Node环境:
在Nodejs环境下有一个独一无二的全局对象:process
,它存储着很多环境变量,我们可以打印一下process.toString()
看看,结果如下:[Object process]
这也是axios依次判断环境的重要依据:
const isHttpAdapterSupported = typeof process !== 'undefined' && utils.kindOf(process) === 'process';
- 浏览器环境
axios通过XMLHttpRequest的存在与否来判断是否处于浏览器环境:
const isXHRAdapterSupported = typeof XMLHttpRequest !== 'undefined';
我们知道了适配逻辑后,首先看看在浏览器环境下,axios对XMLHttpRequest Api的运用有多么出神入化吧~
1. 浏览器环境:XMLHttpRequest
首先,将传入的config对象中的header
、data
取出做处理。其中,对data
是否是formData
类型(文件)做了一些判断:
if (utils.isFormData(requestData)) {
// 判断在PC端还是移动端。
if (platform.isStandardBrowserEnv || platform.isStandardBrowserWebWorkerEnv) {
requestHeaders.setContentType(false); // Let the browser set it
} else {
requestHeaders.setContentType('multipart/form-data;', false); // mobile/desktop app frameworks
}
}
我们来看看判断是否在PC端axios如何来做的:
/**
* Determine if we're running in a standard browser environment
*
* This allows axios to run in a web worker, and react-native.
* Both environments support XMLHttpRequest, but not fully standard globals.
*
* web workers:
* typeof window -> undefined
* typeof document -> undefined
*
* react-native:
* navigator.product -> 'ReactNative'
* nativescript
* navigator.product -> 'NativeScript' or 'NS'
*
* @returns {boolean}
*/
const isStandardBrowserEnv = (() => {
let product;
if (typeof navigator !== 'undefined' && (
(product = navigator.product) === 'ReactNative' ||
product === 'NativeScript' ||
product === 'NS')
) {
return false;
}
return typeof window !== 'undefined' && typeof document !== 'undefined';
})();
/**
* Determine if we're running in a standard browser webWorker environment
*
* Although the `isStandardBrowserEnv` method indicates that
* `allows axios to run in a web worker`, the WebWorker will still be
* filtered out due to its judgment standard
* `typeof window !== 'undefined' && typeof document !== 'undefined'`.
* This leads to a problem when axios post `FormData` in webWorker
*/
const isStandardBrowserWebWorkerEnv = (() => {
return (
typeof WorkerGlobalScope !== 'undefined' &&
self instanceof WorkerGlobalScope &&
typeof self.importScripts === 'function'
);
})();
axios主要是通过navigator.product
来获取当前平台信息。当然,这个属性官方已经标注为废弃,现在可以使用useragent
来获取更丰富的浏览器信息;同时,通过WorkerGlobalScope
可以判断当前环境是否处于webworker中。
当浏览器接收到一个带有 FormData 对象的请求时,它会自动将请求头的 content-type 属性设置为 “multipart/form-data,因此我们并不需要手动添加。
接下来,axios还会根据config.auth
来去配置Authorization。
然后构造一个XMLHttpRequest
对象:
let request = new XMLHttpRequest();
// 这里request.open的第三个参数是async,表示是否异步。这里为true是可以避免阻塞
request.open(config.method.toUpperCase(), buildURL(...), true);
// Set the request timeout in MS
request.timeout = config.timeout; // 设置超时时间
初始化请求后,就开始了各种XMLHttpRequest参数、事件的配置,下面一起来看一下所用到的xhr的事件吧:
onloadend
: axios用onloadend
来代替onreadystatechange
事件。用于处理响应数据。
回顾一下
xhr.readyState
的四种状态:
function onloadend() {
if (!request) {
return;
}
// Prepare the response
const responseHeaders = AxiosHeaders.from(
'getAllResponseHeaders' in request && request.getAllResponseHeaders()
);
const responseData = !responseType || responseType === 'text' || responseType === 'json' ?
request.responseText : request.response;
const response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config,
request
};
// Promise中的回调函数
settle(function _resolve(value) {
resolve(value);
done();
}, function _reject(err) {
reject(err);
done();
}, response);
// Clean up request
request = null;
}
if ('onloadend' in request) {
// Use onloadend if available
request.onloadend = onloadend;
} else {
// Listen for ready state to emulate onloadend
request.onreadystatechange = function handleLoad() {
if (!request || request.readyState !== 4) {
return;
}
// The request errored out and we didn't get a response, this will be
// handled by onerror instead
// With one exception: request that using file: protocol, most browsers
// will return status as 0 even though it's a successful request
if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {
return;
}
// readystate handler is calling before onerror or ontimeout handlers,
// so we should call onloadend on the next 'tick'
setTimeout(onloadend);
};
}
这段代码我们也可以学习到一些冷知识:
- 响应数据如果是json或者文本则存在
xhr.responseText
中 - 如果使用请求协议为file的话,即使成功,响应code也是0
之后这里对浏览器取消、网络请求错误以及请求超时的情况抛出了错误。
-
onabort
: 当 request 被停止时触发,例如当程序调用XMLHttpRequest.abort()
时。 也可以使用 onabort 属性。 -
onerror
:当 request 遭遇错误时触发。 -
ontimeout
:当 request 超时错误时触发
// 浏览器的取消请求,而非手动取消
// Handle browser request cancellation (as opposed to a manual cancellation)
request.onabort = function handleAbort() {
if (!request) {
return;
}
reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request));
// Clean up request
request = null;
};
// Handle low level network errors
request.onerror = function handleError() {
// Real errors are hidden from us by the browser
// onerror should only fire if it's a network error
reject(new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request));
// Clean up request
request = null;
};
// Handle timeout
request.ontimeout = function handleTimeout() {
let timeoutErrorMessage = config.timeout ? 'timeout of ' + config.timeout + 'ms exceeded' : 'timeout exceeded';
const transitional = config.transitional || transitionalDefaults;
if (config.timeoutErrorMessage) {
timeoutErrorMessage = config.timeoutErrorMessage;
}
reject(new AxiosError(
timeoutErrorMessage,
transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,
config,
request));
// Clean up request
request = null;
};
往后,axios准备构造请求所需要的各种信息,包括请求头、cookie等。
我们可以学习到:
xhr.setRequestHeader(key, val)
:此方法用于设置HTTP请求头部,必须在 open() 方法和 send() 之间调用。
// Add headers to the request
if ('setRequestHeader' in request) {
utils.forEach(requestHeaders.toJSON(), function setRequestHeader(val, key) {
request.setRequestHeader(key, val);
});
}
-
xhr.withCredentials
:该属性指示了是否该使用类似 cookie、Authorization 标头或者 TLS 客户端证书等凭据进行跨域访问(Acess-Control)请求。而且,当值为false
的时候,来自不同域的xhr响应无法为其域设置Cookie。当值为true
时,获取的第三方cookie 仍然遵循同源协议,因此请求的脚本无法通过document.cookie
或者响应标头访问。 -
xhr.responseType
:指定响应中包含的数据类型。
// 一般不需要手动设置
if (responseType && responseType !== 'json') {
request.responseType = config.responseType;
}
倒数第二步,axios对文件上传、下载的进度监听以及请求的取消也作了支持:
-
xhr.onprogress
:当请求接收到数据时被周期性触发(下载场景)。 -
xhr.upload
:该属性是一个XMLHttpRequestUpload 对象,支持onloadstart
、onabort
、onprogress
等事件监听,用于表示上传文件的进度。 -
xhr.abort()
:如果该请求已被发出,XMLHttpRequest.abort() 方法将终止该请求。
最后,axios对传入的URL协议进行了校验,并且通过request.send(requestData)
发送请求。
整个流程示意图如下:
2. Node环境:http模块
Node下的http请求有点复杂且不常用,这里只做简易说明:
// 使用events模块来实现事件监听
const emitter = new EventEmitter();
// 事件注册
emitter.once('abort', reject);
// 协议解析
const parsed = new URL(fullPath, 'http://localhost');
const protocol = parsed.protocol || supportedProtocols[0];
// header设置
// ......
// 流式处理data
if (utils.isSpecCompliantForm(data)) {// 支持符合规范的FormData对象
data = formDataToStream(data, (formHeaders) => {
headers.set(formHeaders);
});
// support for https://www.npmjs.com/package/form-data api
} else if (utils.isFormData(data) && utils.isFunction(data.getHeaders)) {
//...
} else if (utils.isBlob(data)) {
data.size && headers.setContentType(data.type || 'application/octet-stream');
headers.setContentLength(data.size || 0);
data = stream.Readable.from(readBlob(data));
} else if (data && !utils.isStream(data)) {
if (Buffer.isBuffer(data)) {
// Nothing to do...
} else if (utils.isArrayBuffer(data)) {
data = Buffer.from(new Uint8Array(data));
} else if (utils.isString(data)) {
data = Buffer.from(data, 'utf-8');
} else {
// error
}
// transport: http或者https模块
// Create the request
req = transport.request(options, handler)
req.on('error', ...);
// set tcp keep alive to prevent drop connection by peer
req.on('socket',...)
3.axios如何实现cancellation?
在了解axios如何终止请求之前,我们先来看一下Web环境下,使用fetch api进行终止请求的例子:
abortBtn.addEventListener("click", () => {
if (controller) {
controller.abort();
console.log("中止下载");
}
});
function fetchVideo() {
controller = new AbortController();
const signal = controller.signal;
fetch(url, { signal })
.then((response) => {
console.log("下载完成", response);
})
.catch((err) => {
console.error(`下载错误:${err.message}`);
});
}
上述例子通过AbortController
构造函数,实现了请求终止。AbortController
接口表示一个控制器对象,允许你根据需要中止一个或多个 Web 请求。
那么在axios中,也是利用了AbortController接口,我们以下面的简单例子来打断点一步一步来探讨axios是如何中断请求的吧。
const controller = new AbortController();
axios.get('/foo/bar', {
signal: controller.signal
})
// cancel the request
controller.abort()
还记得在dispatchRequest
方法中最开始的throwIfCancellationRequested(config)
函数嘛:
function throwIfCancellationRequested(config) {
if (config.cancelToken) {
config.cancelToken.throwIfRequested();
}
if (config.signal && config.signal.aborted) {
throw new CanceledError(null, config);
}
}
这里对当前的请求状态做了判断,如果已经是被中断了,则直接抛出CanceledError
。
但是目前我们的config.signal
的状态是仍未被中断,因此继续执行:
{
aborted: false, // 还没被中断
onabort: null
reason: undefined
}
之后,在XMLHTTPRequest建立的过程中,还设置了abort
的事件监听:
if (config.cancelToken || config.signal) {
// Handle cancellation
// eslint-disable-next-line func-names
onCanceled = cancel => {
if (!request) {
return;
}
reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);
request.abort();
request = null;
};
config.cancelToken && config.cancelToken.subscribe(onCanceled);
if (config.signal) {
config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
}
}
当config.signal
存在时,给config.signal.onabort
设置回调函数onCanceled
,用于监听AbortController.abort()
触发的时候,执行xhr.abort()
来终止HTTP请求,从而执行xhr.onabort
的回调函数。
目前,我们还未中断请求,因此只会给signal绑定上事件监听器。
然后我们运行controller.abort()
,此时config.signal.aborted
为true,触发signal身上的onCanceled
函数。
总而言之,AbortController
相当于外层的事件总线,监听自身的abort()
方法,当AbortController.abort()
触发后,才会去执行xhr.abort()
,最终执行XMLHTTPRequest的onabort
钩子。
4. axios如何实现ProgressCaptrue
axios如何实现进度功能,其实在上述步骤中已经明了了。主要是通过xhr.onprogress
以及xhr.upload.onprogress
两个事件来实现。
axios中对于进度的处理函数如下:
function progressEventReducer(listener, isDownloadStream) {
// 已经传入的字节
let bytesNotified = 0;
const _speedometer = speedometer(50, 250);
return e => {
const loaded = e.loaded;
const total = e.lengthComputable ? e.total : undefined;
const progressBytes = loaded - bytesNotified;
const rate = _speedometer(progressBytes);
const inRange = loaded <= total;
bytesNotified = loaded;
const data = {
loaded,
total,
progress: total ? (loaded / total) : undefined,
bytes: progressBytes,
rate: rate ? rate : undefined,
estimated: rate && total && inRange ? (total - loaded) / rate : undefined,
event: e
};
data[isDownloadStream ? 'download' : 'upload'] = true;
listener(data);
};
}
5. axios如何实现Intercepters?
axios提供了一个非常灵活的中间件系统,可以使用拦截器来处理请求和响应。 拦截器是一个可选的中间件,可以在请求或响应的生命周期中调用,并且可以在请求或响应的任何阶段进行干预。
下面是一个官方的使用案例:
// Add a request interceptor
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
// Add a response interceptor
axios.interceptors.response.use(function (response) {
// Do something with response data
return response;
}, function (error) {
// Do something with response error
return Promise.reject(error);
});
首先我们需要知道,axios.interceptors
是一个什么东西?还记得在第一节中,Axios
构造函数中的属性有哪些嘛。其中就包含着拦截器~
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
我们可以在lib/core/InterceptorManager.js
中找到InterceptorManager
类:
class InterceptorManager {
constructor() {
this.handlers = [];
}
/**
* 往栈中添加拦截器
*
* @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
*/
use(fulfilled, rejected, options) {
this.handlers.push({
fulfilled,
rejected,
synchronous: options ? options.synchronous : false,
runWhen: options ? options.runWhen : null
});
return this.handlers.length - 1;
}
/**
* 移除对应id的拦截器
*
* @param {Number} id The ID that was returned by `use`
*
* @returns {Boolean} `true` if the interceptor was removed, `false` otherwise
*/
eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
}
/**
* Clear all interceptors from the stack
*
* @returns {void}
*/
clear() {
if (this.handlers) {
this.handlers = [];
}
}
/**
* 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
*
* @returns {void}
*/
forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
}
}
拦截器设置好后,当触发axios请求的时候,在Axios.request
方法中:
// filter out skipped interceptors
const requestInterceptorChain = [];
let synchronousRequestInterceptors = true;
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
// 拦截器还可以设置是否执行
if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
return;
}
// 是否允许拦截器同步执行
synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
// 【注意这里是unshift】注入到InterceptorChain中。此时的Chain为[fulfilled, rejected]
requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
});
const responseInterceptorChain = [];
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
// 【注意这里是push】
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});
if (!synchronousRequestInterceptors) {
const chain = [dispatchRequest.bind(this), undefined];
chain.unshift.apply(chain, requestInterceptorChain);
chain.push.apply(chain, responseInterceptorChain);
len = chain.length;
promise = Promise.resolve(config);
while (i < len) {
promise = promise.then(chain[i++], chain[i++]);
}
return promise;
}
这里通过数组来依次放置拦截器。让我们来好好探讨一下这里的放置顺序问题。以上述官方代码为例,此时chain
中的顺序为[requestInterceptorFulfilled, requestInterceptorRejected, dispatchRequest, undefined, responseInterceptorFulfilled, responseInterceptorChainRejected]
。
这里要注意的是,如果你添加了多个拦截器,比如请求拦截器1、2以及响应拦截器3、4,则最后执行顺序为2,1,request,3,4
。
之后,通过promise = promise.then(...)
的形式,设计成了一条promise调用链,便于使用者最后还是可以去用then,catch方法做处理。
一句话总结,axios通过数组的形式,让用户以promise.fulfilled、promise.rejected的形式来添加拦截器。
总结
axios是一个很老很经典的请求库,源码也不多,相对来说比较容易阅读。
阅读完这篇文章,相信读者们也对axios内部的执行流程、适配器层、请求中断、进度捕获以及拦截器的实现有了一定的了解,在继续学习fetch
、umi-request
等新的请求api、第三方库的过程中,也一定可以有所帮助的。
我是「盐焗乳鸽还要香锅」,喜欢我的文章欢迎关注噢。