前几天面试的时候面试官问了两个问题:
axios
底层封装的是什么?- 什么状况下
axios
会报错?
在此之前只看过 axios
底层 Promise
的天然 G 了,面试完就开始看 axios
的源代码。
本文基于 axios v1.1.3
底层封装
首先来看看 axios
底层到底封装的什么
axios
底层封装的申请有两种:XMLHttpRequest
和 Node.js
的 http/https
,位于 lib/adapters
文件夹内。接下来咱们以 XMLHttpRequest
的封装为例看下什么状况下会报错
报错状况
XMLHttpRequest
的封装在 lib/adapters/xhr.js
文件夹内的 xhrAdapter
,该函数间接返回一个 Promise
export default function xhrAdapter(config) { return new Promise(function dispatchXhrRequest(resolve, reject) { ... });
与报错相干的逻辑如下
export default function xhrAdapter(config) { return new Promise(function dispatchXhrRequest(resolve, reject) { ... // 初始化一个 XMLHttpRequest 对象 let request = new XMLHttpRequest(); ... // 初始化一个新创建的申请 request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true); ... // 用于解决申请胜利时的状况 function onloadend() { ... settle(function _resolve(value) { resolve(value); done(); }, function _reject(err) { reject(err); done(); }, response); ... } // 申请胜利时的回调 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; } if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) { return; } setTimeout(onloadend); }; } // 解决勾销申请 request.onabort = function handleAbort() { if (!request) { return; } reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request)); ... }; // 解决网络谬误 request.onerror = function handleError() { reject(new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request)); ... }; // 解决超时 request.ontimeout = function handleTimeout() { ... reject(new AxiosError( timeoutErrorMessage, transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED, config, request)); ... }; // 发送申请 request.send(requestData || null); });}
能够看到 onabort
onerror
ontimeout
三个回调都是间接解决一下而后调用 reject
,然而 onloadend
并不是这样,咱们来认真看一下这一部分
// 用于解决申请胜利时的状况 function onloadend() { ... settle(function _resolve(value) { resolve(value); done(); }, function _reject(err) { reject(err); done(); }, response); ... } // 申请胜利时的回调 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; } if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) { return; } setTimeout(onloadend); }; }
源代码中先是定义了一个 onloadend
函数,接下来判断 request
对象中是否有 onloadend
属性,集体猜想可能是为了兼容旧版本的浏览器。如果不存在这个属性,则走另一种可能,这个待会再说。持续看 onloadend
函数,外面的外围就是调用 settle
函数,此函数位于 lib/core/settle.js
。
export default function settle(resolve, reject, response) { const validateStatus = response.config.validateStatus; if (!response.status || !validateStatus || validateStatus(response.status)) { resolve(response); } else { reject(new AxiosError( 'Request failed with status code ' + response.status, [AxiosError.ERR_BAD_REQUEST, AxiosError.ERR_BAD_RESPONSE][Math.floor(response.status / 100) - 4], response.config, response.request, response )); }}
能够看到 if (!response.status || !validateStatus || validateStatus(response.status))
,即当以下三个条件满足其一时即可 resolve
- 服务器返回的响应中不存在状态码或状态码为 0
axios
的申请配置中validateStatus
属性为空validateStatus
函数返回true
否则则 reject
。那么 validateStatus
又是什么?咱们来看 axios
的默认申请配置 default
,位于 lib/defaults/index.js
文件内
const defaults = { validateStatus: function validateStatus(status) { return status >= 200 && status < 300; },};
能够看到,默认的 validateStatus
是一个函数,当状态码在 [200,300) 时返回 true
,否则返回 false
。
在 XMLHttpRequest
对象触发事件的程序中 onabort
ontimeout
onerror
在 onloadend
之前,所以如果产生了异样,则在 onloadend
执行之前曾经调用了 reject
,onloadend
中即便调用 resolve
也没有作用了。如果申请胜利则由 onloadend
进行解决。
接下来咱们来看 XMLHttpRequest
对象中没有 onloadend
属性的时候
// 申请胜利时的回调 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; } if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) { return; } setTimeout(onloadend); }; }
依据源代码中的正文能够晓得此处通过 onreadystatechange
来模仿 onloadend
。在这里 readyState
不为 4 则间接返回,不进行任何操作。readyState
为 4 代表着申请曾经完结了,无论胜利还是失败。if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0))
是因为如果应用 file:
协定,则即便胜利申请,状态码也会是 0,所以如果状态码是 0 并且不是走的 file:
协定则间接返回,接下来将 onloadend
退出到宏工作队列中去。
通过控制台模仿可知模仿的 onloadend
产生在 onloadend
之后
总结
axios
默认底层封装了 XMLHttpRequest
和 http/https
库,默认状况下只有以下四种状况会报错
- 勾销申请
- 网络谬误
- 超时
- 状态码不在 [200,300) 区间内