詳解axios是如何處理異常的
axios 中的正常請(qǐng)求
axios 中當(dāng)請(qǐng)求服務(wù)正常返回時(shí),會(huì)落入 .then() 方法中。
axios.get('https://httpstat.us/200')
.then(res => {
console.log(res)
})
效果如下:

axios 會(huì)把響應(yīng)結(jié)果包裝在返回的 Response 對(duì)象的 data 屬性中,除此之外:
- config:即請(qǐng)求配置
- headers:響應(yīng)頭數(shù)據(jù)(AxiosHeaders 對(duì)象)
- request:請(qǐng)求實(shí)例。瀏覽器環(huán)境就是 XMLHttpRequest 對(duì)象
- status:HTTP 狀態(tài)碼。本案例是 200,表示請(qǐng)求成功處理了
- statusText: 狀態(tài)碼的文字說(shuō)明
axios 中的異常請(qǐng)求
axios 中的異常請(qǐng)求分 2 類:有響應(yīng)異常請(qǐng)求和無(wú)響應(yīng)的異常請(qǐng)求。
有響應(yīng)的異常
當(dāng)返回的 HTTP 狀態(tài)碼是 2xx 之外的是狗,就會(huì)進(jìn)入 axios 的 .catch() 方法中。
403 響應(yīng):
axios.get('https://httpstat.us/403')
.catch(err => {
console.log(err)
})
效果:

500 響應(yīng):
axios.get('https://httpstat.us/500')
.catch(err => {
console.log(err)
})
效果:

以上 2 個(gè)場(chǎng)景,返回的都是一個(gè) AxiosError 對(duì)象,它繼承自 Error。相比 Error,AxiosError 除了常規(guī)的 code、message、name 和 stack 屬性(非標(biāo)準(zhǔn))外,還包含 config、request 和 reponse:
- response 就是響應(yīng)對(duì)象,與正常請(qǐng)求時(shí)返回的響應(yīng)對(duì)象完全一致
- config 和 request 與 Response 對(duì)象里的一樣——前者是請(qǐng)求配置,后者則是底層的請(qǐng)求對(duì)象
自定義 validateStatus()
當(dāng)然,對(duì)于有響應(yīng)的請(qǐng)求,2xx 狀態(tài)碼進(jìn)入 then,之外的狀態(tài)碼進(jìn)入 catch 是 axios 的默認(rèn)配置——通過 validateStatus() 設(shè)置的。
// `validateStatus` defines whether to resolve or reject the promise for a given
// HTTP response status code. If `validateStatus` returns `true` (or is set to `null`
// or `undefined`), the promise will be resolved; otherwise, the promise will be
// rejected.
validateStatus: function (status) {
return status >= 200 && status < 300; // default
},
在 axios 內(nèi)部,當(dāng)接收到響應(yīng)后,會(huì)將響應(yīng)碼傳入 validateStatus() 函數(shù)校驗(yàn)。返回 true,就表示請(qǐng)求成功,否則表示請(qǐng)求失敗。
你可以自由調(diào)整這里的判斷,決定哪類響應(yīng)可以作為成功的請(qǐng)求處理。
比如,將返回狀態(tài)碼 4xx 的請(qǐng)求也看做是成功的。
axios.get('https://httpstat.us/404', {
validateStatus: function (status) {
return status < 500; // Resolve only if the status code is less than 500
}
})
.then(res => {
console.log(res)
})
效果:

我們?cè)O(shè)置可以將 validateStatus 設(shè)置為 null,將所有有響應(yīng)返回的請(qǐng)求都看作是成功的,這樣也能進(jìn)入 .then() 中處理了。
axios.get('https://httpstat.us/500', {
validateStatus: null
})
.then(res => {
console.log(res)
})
效果:

無(wú)響應(yīng)的異常
不過某些請(qǐng)求是沒有響應(yīng)返回的。比如:網(wǎng)絡(luò)中斷、跨域錯(cuò)誤、超時(shí)、取消請(qǐng)求等。這類異常請(qǐng)求都沒有響應(yīng)返回,但都會(huì)落入到 .catch() 里。
網(wǎng)絡(luò)中斷
先以網(wǎng)絡(luò)中斷的情況舉例。
axios.get('https://httpstat.us/200?sleep=10000', {
})
.catch(err => {
console.log(err)
})
我們模擬了一個(gè)耗時(shí) 10s 的請(qǐng)求,在此期間,我們將電腦的網(wǎng)絡(luò)斷掉。就能看到效果。

這個(gè)時(shí)候可以發(fā)現(xiàn),catch() 中接收到 Axios 對(duì)象是沒有 response 屬性的,說(shuō)明沒有服務(wù)響應(yīng)。同時(shí),錯(cuò)誤信息是“Network Error”,也就是網(wǎng)絡(luò)服務(wù)。
當(dāng)然,無(wú)效地址以及跨域錯(cuò)誤,也報(bào)錯(cuò) “Network Error”。
超時(shí)報(bào)錯(cuò)
再演示一個(gè)請(qǐng)求超時(shí)的案例。
axios.get('https://httpstat.us/200?sleep=10000', {
timeout: 1000
})
.catch(err => {
console.log(err)
})
我們模擬了個(gè) 10s 返回的請(qǐng)求,而超時(shí)限制設(shè)置在了 1s。運(yùn)行代碼,效果如下:

顯而易見,錯(cuò)誤里依然沒有 response 屬性,錯(cuò)誤的消息也很清晰的說(shuō)明了問題:"過了 1s 的超時(shí)限制了"。
取消請(qǐng)求
axios 中還提供了取消請(qǐng)求的方案。
const controller = new AbortController();
axios.get('https://httpstat.us/200?sleep=10000', {
signal: controller.signal
})
.catch(err => {
console.log(err)
})
controller.abort();
效果如下:

catch() 捕獲到的是一個(gè) CanceledError 對(duì)象,它繼承了 AxiosError,這樣我們就能單獨(dú)判斷這類自動(dòng)取消的情況了。注意,這里依然是沒有 response 屬性的。
當(dāng)然,axios 中還有一個(gè)舊的取消方案——使用 CancelToken。
axios.get('https://httpstat.us/200?sleep=10000', {
cancelToken: source.token
})
.catch(res => {
console.log(res)
})
source.cancel();

相比較于 AbortController 觸發(fā)的取消,少了 config 和 request 屬性。
以上,我們就列完了 aioxs 中各類異常請(qǐng)求的場(chǎng)景及表現(xiàn)。官方倉(cāng)庫(kù) README 的 Handling Errors 也對(duì)此做了歸納。
axios.get('/user/12345')
.catch(function (error) {
if (error.response) {
// 請(qǐng)求發(fā)出,并且得到服務(wù)器響應(yīng)
// 響應(yīng)碼在 2xx 之外(默認(rèn))
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
// 請(qǐng)求發(fā)出,但沒有響應(yīng)返回
// `error.request` 對(duì)應(yīng)底層請(qǐng)求對(duì)象。瀏覽器環(huán)境是 XMLHttpRequest 實(shí)例,Node.js 環(huán)境下則是 http.ClientRequest 實(shí)例
console.log(error.request);
} else {
// 在請(qǐng)求準(zhǔn)備/響應(yīng)處理階段出錯(cuò)了
console.log('Error', error.message);
}
console.log(error.config);
});
接下來(lái)就來(lái)分析 axios 中是如何實(shí)現(xiàn)請(qǐng)求的異常處理的。
源碼分析
我們還是以 axios 的瀏覽器端實(shí)現(xiàn)(lib/adapters/xhr.js)為例。
AxiosError
通過前面的學(xué)習(xí),我們知道 axios 拋出的異常是基于 Error 基類封裝的 AxiosError,其源代碼位于 /lib/core/AxiosError.js。
function AxiosError(message, code, config, request, response) {
// 1)
Error.call(this);
// 2)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
} else {
this.stack = (new Error()).stack;
}
// 3)
this.message = message;
this.name = 'AxiosError';
// 4)
code && (this.code = code);
config && (this.config = config);
request && (this.request = request);
response && (this.response = response);
}
簡(jiǎn)單做一些說(shuō)明:
- Error.call(this) 的作用類似調(diào)用父級(jí)構(gòu)造函數(shù),AxiosError 實(shí)例原型也成 Error 實(shí)例了
- 收集報(bào)錯(cuò)棧信息,優(yōu)先以 Error.captureStackTrace 方式收集,方便排查問題
- 設(shè)置常規(guī)屬性 message 和 name
- 擴(kuò)展出 code、code、code 和 response,這些都是可選的
當(dāng)然 AxiosError 還有其他代碼,因?yàn)楸疚牟簧婕埃筒辉儋樖觥?/p>
介紹完 AxiosError,就可以分析 axios 中是如何拋出 AxiosError 的了。
XMLHttpRequest 對(duì)象
在能夠拋出異常之前,我們需要先創(chuàng)建請(qǐng)求對(duì)象 request。
// https://github.com/axios/axios/blob/v1.6.8/lib/adapters/xhr.js#L76 let request = new XMLHttpRequest();
瀏覽器環(huán)境,request 就是 XMLHttpRequest 實(shí)例,接下來(lái)的異常處理都是基于 request 上的監(jiān)聽事件捕獲的。
無(wú)響應(yīng)異常的處理
接下來(lái),我們先講無(wú)響應(yīng)異常的處理,因?yàn)樗鼈兊南鄬?duì)邏輯比較簡(jiǎn)單。
網(wǎng)絡(luò)異常
這類異常包括:網(wǎng)絡(luò)中斷、跨域錯(cuò)誤以及請(qǐng)求地址錯(cuò)誤。通過監(jiān)聽 request 的 onerror 事件實(shí)現(xiàn):
// /v1.6.8/lib/adapters/xhr.js#L158-L166
// 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;
};
直接返回了一個(gè) reject 狀態(tài)的 Promise,表示請(qǐng)求失敗。并返回了 CODE 值為 ERR_NETWORK 的 AxiosError 對(duì)象。
超時(shí)處理
再來(lái)看看對(duì)超時(shí)的處理,監(jiān)聽了 ontimeout 事件。
// /v1.6.8/lib/adapters/xhr.js#L168-L183
// 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;
};
處理也很簡(jiǎn)單,同樣是 reject Promise,同時(shí)拋出一個(gè) CODE 值為 ECONNABORTED 的 AxiosError 對(duì)象。
transitional 配置對(duì)象是為了向后兼容才保留的,已不再推薦使用,所以你可以忽略這部分你的判斷邏輯。
另外,你還可以通過傳入 config.timeoutErrorMessage 配置,自定義超時(shí)報(bào)錯(cuò)消息。
取消請(qǐng)求
取消請(qǐng)求依賴的是監(jiān)聽 onabort 事件。
// /v1.6.8/lib/adapters/xhr.js#L146-L156
// 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;
};
當(dāng)你調(diào)用 request 上的 abort() 方法時(shí),就會(huì)觸發(fā)這個(gè)事件調(diào)用。
在 axios 內(nèi)部,不管你是通過 signal 還是通過 cancelToken(已棄用),內(nèi)部都是通過調(diào)用 request.abort() 來(lái)中止請(qǐng)求的。
取消請(qǐng)求的報(bào)錯(cuò) CODE 值跟超時(shí)一樣也是 ECONNABORTED,不過報(bào)錯(cuò)消息是“Request aborted”。這樣你就能區(qū)分這次請(qǐng)求是瀏覽器取消的還是人工取消的了。
// /v1.6.8/lib/adapters/xhr.js#L231-L247
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);
}
}
再來(lái)看看,有響應(yīng)的異常處理邏輯。
有響應(yīng)異常的處理
axios 內(nèi)部通過監(jiān)聽 onloadend 事件來(lái)處理有響應(yīng)的異常請(qǐng)求。
// /v1.6.8/lib/adapters/xhr.js#L125 request.onloadend = onloadend
不管當(dāng)前請(qǐng)求是否成功,onloadend 回調(diào)總是會(huì)調(diào)用,這里其實(shí)是可以使用 onload 事件替代的。
request.onload = onloadend
之所以有這部分邏輯是為了向后兼容,因?yàn)?axios 中這部分的完整邏輯是這樣的。
if ('onloadend' in request) {
// Use onloadend if available
request.onloadend = onloadend;
} else {
// Listen for ready state to emulate onloadend
request.onreadystatechange = function handleLoad() {
// ...
// readystate handler is calling before onerror or ontimeout handlers,
// so we should call onloadend on the next 'tick'
setTimeout(onloadend);
}
}
OK,我們繼續(xù)看 onloadend 函數(shù)的內(nèi)容:
// /v1.6.8/lib/adapters/xhr.js#L92-L121
function onloadend() {
// 1)
if (!request) {
return;
}
// 2)
// 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
};
// 3)
settle(function _resolve(value) {
resolve(value);
done();
}, function _reject(err) {
reject(err);
done();
}, response);
// Clean up request
request = null;
}
- 這里做了 request 的非空判斷。因?yàn)?onloadend 在調(diào)用之前,可以已經(jīng)在其他的回調(diào)事件中處理了,直接返回即可
- 這里則是準(zhǔn)備返回的響應(yīng)數(shù)據(jù)。先收集響應(yīng)頭數(shù)據(jù),再獲得響應(yīng)數(shù)據(jù),最后拼成 Respoonse 對(duì)象返回。注意,當(dāng) responseType 是 "json" 時(shí),響應(yīng)數(shù)據(jù)返回的是 request.responseText,是個(gè)字符串,這會(huì)在下一步處理。
- 這里我們將拼接的 response 交由 settle 函數(shù)處理,并由它決定最終是成功請(qǐng)求(
resolve(err))還是失敗請(qǐng)求(reject(err))
settle 函數(shù)位于 lib/core/settle.js:
/**
* Resolve or reject a Promise based on response status.
*
* @param {Function} resolve A function that resolves the promise.
* @param {Function} reject A function that rejects the promise.
* @param {object} response The response.
*
* @returns {object} The response.
*/
export default function settle(resolve, reject, response) {
// 1)
const validateStatus = response.config.validateStatus;
// 2)
if (!response.status || !validateStatus || validateStatus(response.status)) {
resolve(response);
// 3)
} 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
));
}
}
- 我們首先拿到了 validateStatus 配置。這是我們判斷請(qǐng)求成功與否的關(guān)鍵
- 這個(gè) if 通過就把傳入的 response 直接丟出去,表示請(qǐng)求成功了。跟這個(gè)判斷邏輯,我們可以知道,當(dāng) validateStatus 為空(
null或undefined),所有響應(yīng)都會(huì)認(rèn)為是成功的被返回 - 否則,沒有通過校驗(yàn)?zāi)蔷捅硎菊?qǐng)求失敗了。報(bào)錯(cuò)消息類似
'Request failed with status code xxx';4xx 狀態(tài)碼的返回 CODE 是 ERR_BAD_REQUEST,5xx 狀態(tài)碼的返回 CODE 是 ERR_BAD_RESPONSE;最后我們還把 response 作為 AxiosError 的 response 屬性傳入了進(jìn)來(lái)
至此,我們就講完了 axios 中的異常處理邏輯了。
總結(jié)
本文介紹了 axios 請(qǐng)求過程中可能會(huì)出現(xiàn)的各種異常場(chǎng)景。
axios 異常場(chǎng)景按照有無(wú)響應(yīng)分 2 類:有響應(yīng)異常和無(wú)響應(yīng)異常。有響應(yīng)異常就是指那些能成功接收到服務(wù)器響應(yīng)狀態(tài)碼的請(qǐng)求,包括常見的 2xx、4xx 和 5xx;無(wú)響應(yīng)異常則包括網(wǎng)絡(luò)中斷、無(wú)效地址、跨域錯(cuò)誤、超時(shí)、取消等場(chǎng)景下的錯(cuò)誤,這些都是接受不到服務(wù)器響應(yīng)的。
然后,我們從瀏覽器端實(shí)現(xiàn)出發(fā),介紹了 AxiosError、分析了拋出 AxiosError 異常的時(shí)機(jī)與方式。
以上就是詳解axios是如何處理異常的的詳細(xì)內(nèi)容,更多關(guān)于axios處理異常的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JS中Json數(shù)據(jù)的處理和解析JSON數(shù)據(jù)的方法詳解
JSON (JavaScript Object Notation)一種簡(jiǎn)單的數(shù)據(jù)格式,比xml更輕巧,這篇文章主要介紹了JS中Json數(shù)據(jù)的處理和解析JSON數(shù)據(jù)的方法詳解的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06
JavaScript實(shí)現(xiàn)的in_array函數(shù)
這篇文章主要介紹了JavaScript實(shí)現(xiàn)的in_array函數(shù),用于判斷一個(gè)值是否在數(shù)組中,類似PHP的in_array函數(shù),需要的朋友可以參考下2014-08-08
javascript中for/in循環(huán)及使用技巧
如果您希望一遍又一遍地運(yùn)行相同的代碼,并且每次的值都不同,那么使用循環(huán)是很方便的,本篇文章給大家介紹javascript中for/in循環(huán)及使用技巧 ,需要的朋友可以參考下2015-09-09
探析瀏覽器執(zhí)行JavaScript腳本加載與代碼執(zhí)行順序
本文主要基于向HTML頁(yè)面引入JavaScript的幾種方式,分析HTML中JavaScript腳本的執(zhí)行順序問題,通過本文給大家分享瀏覽器執(zhí)行JavaScript腳本加載與代碼執(zhí)行順序,對(duì)瀏覽器執(zhí)行javascript及執(zhí)行順序相關(guān)知識(shí)感興趣的朋友一起學(xué)習(xí)吧2016-01-01
JavaScript實(shí)現(xiàn)猜數(shù)字游戲
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)猜數(shù)字游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-05-05
js閉包的6種應(yīng)用場(chǎng)景總結(jié)
如果一個(gè)函數(shù)訪問了此函數(shù)的父級(jí)及父級(jí)以上的作用域變量,那么這個(gè)函數(shù)就是一個(gè)閉包,本文將給大家分享js閉包的6種應(yīng)用場(chǎng)景,文中有詳細(xì)的代碼示例,需要的朋友可以參考下2023-09-09
javaScript Array(數(shù)組)相關(guān)方法簡(jiǎn)述
javaScript Array(數(shù)組)相關(guān)方法簡(jiǎn)述,讓大家更快的熟悉array數(shù)組的用法。2009-07-07

