Axios异常处理机制:网络错误、状态码异常与超时处理全解析
<|begin▁of▁sentence|># 1. 前言
在上一篇文章中,我们实现了`axios`的基础功能,但到目前为止,我们都只是实现了一个很简单的`axios`,一个真正库的功能远比我们目前实现的要复杂,那么从这篇文章开始,我们就来一步步完善这个`axios`,把它向真正的`axios`库迈进。
那么,这篇文章我们就来实现`axios`的异常处理机制。我们都知道,在平时开发中,我们处理`HTTP`请求时不可避免的会请求失败或者遇到异常,那么此时我们就需要对异常进行处理,而`axios`中也会抛出异常,并且我们还可以对其进行配置。接下来,我们就来实现`axios`的异常处理机制。
# 2. 需求分析
在`axios`的核心流程中,我们通过`XMLHttpRequest`对象的`onerror`事件属性可以捕获到网络异常;通过`XMLHttpRequest`对象的`onreadystatechange`事件属性可以判断`HTTP`状态码是否在`200-300`之间,如果不在,则也是发生了异常。
当捕获到异常后,我们都要`reject`回一个错误,并且返回的错误是一个`AxiosError`类实例,该实例中包含错误配置`config`、错误码`code`、`XMLHttpRequest`对象实例`request`以及响应`response`。
另外,我们还可以配置`validateStatus`属性来自定义判断`HTTP`状态码是否合法,如果不合法,也同样抛出异常。
# 3. 异常处理
根据需求分析,我们分以下几步来实现:
1. 创建`AxiosError`类;
2. 在`xhr`函数中捕获异常并抛出;
3. 添加`validateStatus`配置;
## 3.1 创建 AxiosError 类
我们先来创建`AxiosError`类,我们在`src`目录下创建`helpers`文件夹,然后在其中创建`error.ts`文件,在该文件中创建`AxiosError`类:
```typescript
// src/helpers/error.ts
export interface AxiosError extends Error {
config: AxiosRequestConfig;
code?: string;
request?: any;
response?: AxiosResponse;
isAxiosError: boolean;
}
export function createError(
message: string,
config: AxiosRequestConfig,
code?: string,
request?: any,
response?: AxiosResponse
): AxiosError {
const error = new Error(message) as AxiosError;
return extend(error, {
config,
code,
request,
response,
isAxiosError: true,
});
}
```
我们首先定义了`AxiosError`接口,它继承于`Error`类型,拥有`Error`所有的属性,另外还添加了`config`、`code`、`request`、`response`、`isAxiosError`这些属性。
然后我们定义了`createError`方法,该方法用于创建`AxiosError`类的实例对象,它内部调用了`Error`类,并把我们传入的参数合并到`Error`实例中,并且还添加了`isAxiosError`属性用于标识这是一个`AxiosError`类型的错误。
## 3.2 在 xhr 函数中捕获异常并抛出
异常分为两种:网络异常和`HTTP`状态码异常。
- 网络异常:当网络出现异常(比如断网)的时候会触发`XMLHttpRequest`对象实例的`error`事件,我们可以在`onerror`的事件回调函数中捕获此类异常。
- `HTTP`状态码异常:当`HTTP`状态码不在`200-300`之间时,我们可以在`onreadystatechange`的事件回调函数中捕获此类异常,并且我们还可以通过配置`validateStatus`属性来自定义判断`HTTP`状态码是否合法。
OK,我们接下来就改写`xhr`函数,在其中添加上异常处理逻辑。
```typescript
// src/xhr.ts
import { createError } from "./helpers/error";
export default function xhr(config: AxiosRequestConfig): AxiosPromise {
return new Promise((resolve, reject) => {
const {
data = null,
url,
method = "get",
headers,
responseType,
timeout,
} = config;
const request = new XMLHttpRequest();
if (responseType) {
request.responseType = responseType;
}
if (timeout) {
request.timeout = timeout;
}
request.open(method.toUpperCase(), url, true);
request.onreadystatechange = function handleLoad() {
if (request.readyState !== 4) {
return;
}
if (request.status === 0) {
return;
}
const responseHeaders = parseHeaders(request.getAllResponseHeaders());
const responseData =
responseType && responseType !== "text"
? request.response
: request.responseText;
const response: AxiosResponse = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config,
request,
};
handleResponse(response);
};
request.onerror = function handleError() {
reject(createError("Network Error", config, null, request));
};
request.ontimeout = function handleTimeout() {
reject(
createError(
`Timeout of ${timeout} ms exceeded`,
config,
"ECONNABORTED",
request
)
);
};
Object.keys(headers).forEach((name) => {
if (data === null && name.toLowerCase() === "content-type") {
delete headers[name];
} else {
request.setRequestHeader(name, headers[name]);
}
});
request.send(data);
function handleResponse(response: AxiosResponse) {
if (response.status >= 200 && response.status < 300) {
resolve(response);
} else {
reject(
createError(
`Request failed with status code ${response.status}`,
config,
null,
request,
response
)
);
}
}
});
}
```
我们添加了`handleResponse`函数,该函数负责处理响应,如果`HTTP`状态码在`200-300`之间,则`resolve`响应`response`,否则就`reject`一个错误,该错误是通过`createError`创建,并且把响应`response`也传了进去。
另外,我们还通过`XMLHttpRequest`对象实例的`onerror`事件属性来捕获网络异常,当网络异常时,我们`reject`一个由`createError`创建的错误。
我们还添加了`ontimeout`事件属性来捕获请求超时异常,当请求超时的时候,我们同样`reject`一个由`createError`创建的错误。
注意:当网络异常的时候,`XMLHttpRequest`对象实例的`readyState`属性并不会变为`4`,所以我们在`onreadystatechange`的事件回调函数中,如果`request.readyState`不为`4`我们直接`return`,并且当`request.status`为`0`时我们也`return`,因为网络异常或者超时异常时该值都为`0`。
## 3.3 添加 validateStatus 配置
另外,我们还希望用户可以配置`validateStatus`选项,让用户可以自定义判断`HTTP`状态码是否合法,如果在用户自定义的规则中状态码是合法的,则正常`resolve(response)`,否则才`reject`错误。
首先,我们在`src/types/index.ts`中的配置对象`AxiosRequestConfig`里添加`validateStatus`属性。
```typescript
// src/types/index.ts
export interface AxiosRequestConfig {
// 新增
validateStatus?: (status: number) => boolean;
}
```
然后,我们在`src/xhr.ts`中的`handleResponse`函数里使用该属性:
```typescript
function handleResponse(response: AxiosResponse) {
if (!validateStatus || validateStatus(response.status)) {
resolve(response);
} else {
reject(
createError(
`Request failed with status code ${response.status}`,
config,
null,
request,
response
)
);
}
}
```
在`handleResponse`函数中,我们判断如果用户配置了`validateStatus`并且`validateStatus`函数返回的值为`true`,或者用户没有配置`validateStatus`,但是原生的`HTTP`状态码在`200-300`之间,我们都`resolve(response)`,否则才`reject`错误。
# 4. 编写 demo
接下来,我们编写 `demo` 来测试下异常处理是否有效。
在 `examples` 目录下创建 `error` 目录,在 `error` 目录下创建 `index.`:
```
< lang="en">
error example
>
```
接着再创建 `app.ts` 作为入口文件:
```typescript
// examples/error/app.ts
import axios from "../../src/index";
// 模拟网络错误
axios({
method: "get",
url: "/error/get1",
})
.then((res) => {
console.log(res);
})
.catch((e) => {
console.log(e);
});
// 模拟状态码错误
axios({
method: "get",
url: "/error/get",
})
.then((res) => {
console.log(res);
})
.catch((e) => {
console.log(e);
});
// 模拟超时错误
axios({
method: "get",
url: "/error/timeout",
timeout: 2000,
})
.then((res) => {
console.log(res);
})
.catch((e) => {
console.log(e);
});
```
接着在 `server.js` 添加新的接口路由:
```javascript
// 模拟网络错误
router.get("/error/get1", function (req, res) {
res.status(500);
res.end();
});
// 模拟状态码错误
router.get("/error/get", function (req, res) {
if (Math.random() > 0.5) {
res.json({
msg: `hello world`,
});
} else {
res.status(500);
res.end();
}
});
// 模拟超时错误
router.get("/error/timeout", function (req, res) {
setTimeout(() => {
res.json({
msg: `hello world`,
});
}, 3000);
});
```
最后在根目录下的 `index.` 中加上启动该 `demo` 的入口:
```
error
```
OK, 我们在命令行中执行:
```bash
# 同时开启客户端和服务端
npm run server | npm start
```
接着打开 `chrome` 浏览器,访问 即可访问我们的 `demo` 了,我们点击 `error`,通过`F12`的控制台我们可以看到:网络错误、状态码错误以及超时错误都已经被捕获到了,并且返回的错误也都包含了错误配置`config`、错误码`code`、`XMLHttpRequest`对象实例`request`以及响应`response`等信息。

# 5. 遗留问题
我们虽然已经实现了异常处理机制,但是目前还是存在一个问题:如果我们先设置请求超时时间为 2 秒,`console.log(e)` 打印出来的 `e` 是一个 `Error` 实例,但是我们再 `console.log(e.message)` 的时候却返回的是 `undefined`,这是为什么呢?
其实原因很简单,因为我们通过 `extend` 方法把 `config`、`code`、`request`、`response`、`isAxiosError` 这些属性挂载到 `Error` 实例上时,并没有把这些属性设置为实例自身的属性,而是将其设置在了实例的 `__proto__` 上,如下:

而 `Error` 实例上自身属性只有 `message` 和 `stack`,所以如果我们通过 `e.message` 访问的时候,访问的是 `Error` 类实例上的 `message` 属性,而我们再创建 `AxiosError` 类实例的时候并没有把 `message` 属性挂载到 `Error` 类实例上,而是挂载到了其 `__proto__` 上,所以访问 `e.message` 的时候返回的是 `undefined`。
那么该如何解决这个问题呢?我们只需要在 `createError` 方法中,在创建好 `error` 对象后,遍历我们所要挂载的对象,将对象上的每一个属性都设置为 `error` 对象自身的属性即可,如下:
```typescript
// src/helpers/error.ts
export function createError(
message: string,
config: AxiosRequestConfig,
code?: string,
request?: any,
response?: AxiosResponse
): AxiosError {
const error = new Error(message) as AxiosError;
return extend(error, {
config,
code,
request,
response,
isAxiosError: true,
});
}
```
改为:
```typescript
// src/helpers/error.ts
export function createError(
message: string,
config: AxiosRequestConfig,
code?: string,
request?: any,
response?: AxiosResponse
): AxiosError {
const error = new Error(message) as AxiosError;
addProperties(error, {
config,
code,
request,
response,
isAxiosError: true,
});
return error;
}
function addProperties(target: any, source: any): void {
for (const key in source) {
if (source.hasOwnProperty(key)) {
target[key] = source[key];
}
}
}
```
我们新增了 `addProperties` 工具方法,该方法用于遍历源对象,将源对象上的属性设置为目标对象自身的属性。然后在 `createError` 方法中创建好 `error` 对象后,调用 `addProperties` 方法将我们想要挂载的对象属性设置为 `error` 对象自身的属性。
这样,我们再访问 `e.message` 的时候就可以正常访问了。
# 6. 总结
本篇文章中,我们实现了 `axios` 的异常处理机制,并且编写了 `demo` 进行了测试。异常处理机制对于一个库来说至关重要,它能够帮助我们快速定位问题所在,所以必须要做好。
下篇文章,我们将会实现 `axios` 的接口扩展功能,敬请期待。
最新文章
- 安全驾驶与车辆保养指南:从检查到保险的全攻略
- 电动汽车革命:从电池突破到智能出行的未来
- 铝合金轻量化汽车车身设计
- 汽车冷却系统维护保养技巧
- 汽车空气滤清器更换周期及保养技巧
- 奔驰驾驶体验豪华舒适
- 激光雷达:自动驾驶的3D火眼金睛与未来挑战
- 安全配置越完善,车险折扣越高?揭秘安全性能与保费的关系
- 车险必知:免赔额解析与第三者责任险投保指南
- 三角警示牌使用指南:正确摆放距离与注意事项
- 智能座舱与固态电池:未来汽车的三大技术变革
- 智能座舱与电动化浪潮下的自动驾驶技术演进
- 汽车胎噪全解析:从源头到静音解决方案
- 机油滤清器保养汽车引擎关键
- 三角警示牌使用指南:安全驾驶必备知识与选购要点
- 域控制器:智能汽车的大脑中枢与未来演进
- 汽车气门保养全攻略:积碳、间隙与油封的维护要点
- 电动化与智能驾驶:动力电池与V2X技术引领汽车变革
- 掌握发动机转速与空气滤清器保养,OBD行车电脑助你省油20%
- 智能座舱到自动驾驶:车联网时代的汽车科技革命
