详解axios是如何处理异常的
作者:zhangbao90s
axios 中的正常请求
axios 中当请求服务正常返回时,会落入 .then() 方法中。
axios.get('https://httpstat.us/200')
.then(res => {
console.log(res)
})
效果如下:

axios 会把响应结果包装在返回的 Response 对象的 data 属性中,除此之外:
- config:即请求配置
- headers:响应头数据(AxiosHeaders 对象)
- request:请求实例。浏览器环境就是 XMLHttpRequest 对象
- status:HTTP 状态码。本案例是 200,表示请求成功处理了
- statusText: 状态码的文字说明
axios 中的异常请求
axios 中的异常请求分 2 类:有响应异常请求和无响应的异常请求。
有响应的异常
当返回的 HTTP 状态码是 2xx 之外的是狗,就会进入 axios 的 .catch() 方法中。
403 响应:
axios.get('https://httpstat.us/403')
.catch(err => {
console.log(err)
})
效果:

500 响应:
axios.get('https://httpstat.us/500')
.catch(err => {
console.log(err)
})
效果:

以上 2 个场景,返回的都是一个 AxiosError 对象,它继承自 Error。相比 Error,AxiosError 除了常规的 code、message、name 和 stack 属性(非标准)外,还包含 config、request 和 reponse:
- response 就是响应对象,与正常请求时返回的响应对象完全一致
- config 和 request 与 Response 对象里的一样——前者是请求配置,后者则是底层的请求对象
自定义 validateStatus()
当然,对于有响应的请求,2xx 状态码进入 then,之外的状态码进入 catch 是 axios 的默认配置——通过 validateStatus() 设置的。
// `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 内部,当接收到响应后,会将响应码传入 validateStatus() 函数校验。返回 true,就表示请求成功,否则表示请求失败。
你可以自由调整这里的判断,决定哪类响应可以作为成功的请求处理。
比如,将返回状态码 4xx 的请求也看做是成功的。
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)
})
效果:

我们设置可以将 validateStatus 设置为 null,将所有有响应返回的请求都看作是成功的,这样也能进入 .then() 中处理了。
axios.get('https://httpstat.us/500', {
validateStatus: null
})
.then(res => {
console.log(res)
})
效果:

无响应的异常
不过某些请求是没有响应返回的。比如:网络中断、跨域错误、超时、取消请求等。这类异常请求都没有响应返回,但都会落入到 .catch() 里。
网络中断
先以网络中断的情况举例。
axios.get('https://httpstat.us/200?sleep=10000', {
})
.catch(err => {
console.log(err)
})
我们模拟了一个耗时 10s 的请求,在此期间,我们将电脑的网络断掉。就能看到效果。

这个时候可以发现,catch() 中接收到 Axios 对象是没有 response 属性的,说明没有服务响应。同时,错误信息是“Network Error”,也就是网络服务。
当然,无效地址以及跨域错误,也报错 “Network Error”。
超时报错
再演示一个请求超时的案例。
axios.get('https://httpstat.us/200?sleep=10000', {
timeout: 1000
})
.catch(err => {
console.log(err)
})
我们模拟了个 10s 返回的请求,而超时限制设置在了 1s。运行代码,效果如下:

显而易见,错误里依然没有 response 属性,错误的消息也很清晰的说明了问题:"过了 1s 的超时限制了"。
取消请求
axios 中还提供了取消请求的方案。
const controller = new AbortController();
axios.get('https://httpstat.us/200?sleep=10000', {
signal: controller.signal
})
.catch(err => {
console.log(err)
})
controller.abort();
效果如下:

catch() 捕获到的是一个 CanceledError 对象,它继承了 AxiosError,这样我们就能单独判断这类自动取消的情况了。注意,这里依然是没有 response 属性的。
当然,axios 中还有一个旧的取消方案——使用 CancelToken。
axios.get('https://httpstat.us/200?sleep=10000', {
cancelToken: source.token
})
.catch(res => {
console.log(res)
})
source.cancel();

相比较于 AbortController 触发的取消,少了 config 和 request 属性。
以上,我们就列完了 aioxs 中各类异常请求的场景及表现。官方仓库 README 的 Handling Errors 也对此做了归纳。
axios.get('/user/12345')
.catch(function (error) {
if (error.response) {
// 请求发出,并且得到服务器响应
// 响应码在 2xx 之外(默认)
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
// 请求发出,但没有响应返回
// `error.request` 对应底层请求对象。浏览器环境是 XMLHttpRequest 实例,Node.js 环境下则是 http.ClientRequest 实例
console.log(error.request);
} else {
// 在请求准备/响应处理阶段出错了
console.log('Error', error.message);
}
console.log(error.config);
});
接下来就来分析 axios 中是如何实现请求的异常处理的。
源码分析
我们还是以 axios 的浏览器端实现(lib/adapters/xhr.js)为例。
AxiosError
通过前面的学习,我们知道 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);
}
简单做一些说明:
- Error.call(this) 的作用类似调用父级构造函数,AxiosError 实例原型也成 Error 实例了
- 收集报错栈信息,优先以 Error.captureStackTrace 方式收集,方便排查问题
- 设置常规属性 message 和 name
- 扩展出 code、code、code 和 response,这些都是可选的
当然 AxiosError 还有其他代码,因为本文不涉及,就不再赘述。
介绍完 AxiosError,就可以分析 axios 中是如何抛出 AxiosError 的了。
XMLHttpRequest 对象
在能够抛出异常之前,我们需要先创建请求对象 request。
// https://github.com/axios/axios/blob/v1.6.8/lib/adapters/xhr.js#L76 let request = new XMLHttpRequest();
浏览器环境,request 就是 XMLHttpRequest 实例,接下来的异常处理都是基于 request 上的监听事件捕获的。
无响应异常的处理
接下来,我们先讲无响应异常的处理,因为它们的相对逻辑比较简单。
网络异常
这类异常包括:网络中断、跨域错误以及请求地址错误。通过监听 request 的 onerror 事件实现:
// /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;
};
直接返回了一个 reject 状态的 Promise,表示请求失败。并返回了 CODE 值为 ERR_NETWORK 的 AxiosError 对象。
超时处理
再来看看对超时的处理,监听了 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;
};
处理也很简单,同样是 reject Promise,同时抛出一个 CODE 值为 ECONNABORTED 的 AxiosError 对象。
transitional 配置对象是为了向后兼容才保留的,已不再推荐使用,所以你可以忽略这部分你的判断逻辑。
另外,你还可以通过传入 config.timeoutErrorMessage 配置,自定义超时报错消息。
取消请求
取消请求依赖的是监听 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;
};
当你调用 request 上的 abort() 方法时,就会触发这个事件调用。
在 axios 内部,不管你是通过 signal 还是通过 cancelToken(已弃用),内部都是通过调用 request.abort() 来中止请求的。
取消请求的报错 CODE 值跟超时一样也是 ECONNABORTED,不过报错消息是“Request aborted”。这样你就能区分这次请求是浏览器取消的还是人工取消的了。
// /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);
}
}
再来看看,有响应的异常处理逻辑。
有响应异常的处理
axios 内部通过监听 onloadend 事件来处理有响应的异常请求。
// /v1.6.8/lib/adapters/xhr.js#L125 request.onloadend = onloadend
不管当前请求是否成功,onloadend 回调总是会调用,这里其实是可以使用 onload 事件替代的。
request.onload = onloadend
之所以有这部分逻辑是为了向后兼容,因为 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,我们继续看 onloadend 函数的内容:
// /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 的非空判断。因为 onloadend 在调用之前,可以已经在其他的回调事件中处理了,直接返回即可
- 这里则是准备返回的响应数据。先收集响应头数据,再获得响应数据,最后拼成 Respoonse 对象返回。注意,当 responseType 是 "json" 时,响应数据返回的是 request.responseText,是个字符串,这会在下一步处理。
- 这里我们将拼接的 response 交由 settle 函数处理,并由它决定最终是成功请求(
resolve(err))还是失败请求(reject(err))
settle 函数位于 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 配置。这是我们判断请求成功与否的关键
- 这个 if 通过就把传入的 response 直接丢出去,表示请求成功了。跟这个判断逻辑,我们可以知道,当 validateStatus 为空(
null或undefined),所有响应都会认为是成功的被返回 - 否则,没有通过校验那就表示请求失败了。报错消息类似
'Request failed with status code xxx';4xx 状态码的返回 CODE 是 ERR_BAD_REQUEST,5xx 状态码的返回 CODE 是 ERR_BAD_RESPONSE;最后我们还把 response 作为 AxiosError 的 response 属性传入了进来
至此,我们就讲完了 axios 中的异常处理逻辑了。
总结
本文介绍了 axios 请求过程中可能会出现的各种异常场景。
axios 异常场景按照有无响应分 2 类:有响应异常和无响应异常。有响应异常就是指那些能成功接收到服务器响应状态码的请求,包括常见的 2xx、4xx 和 5xx;无响应异常则包括网络中断、无效地址、跨域错误、超时、取消等场景下的错误,这些都是接受不到服务器响应的。
然后,我们从浏览器端实现出发,介绍了 AxiosError、分析了抛出 AxiosError 异常的时机与方式。
以上就是详解axios是如何处理异常的的详细内容,更多关于axios处理异常的资料请关注脚本之家其它相关文章!
