前几天面试的时候面试官问了两个问题:
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) 区间内