《Axios错误处理全解析:请求与响应异常捕获实战》
<|begin▁of▁sentence|># 1. 前言
在上一篇文章中,我们实现了`axios`的基础功能,但到目前为止,我们都只实现了正常情况下的功能,也就是服务器返回的状态码是`200`的情况,那如果服务器返回的状态码不是`200`而是`400`、`500`等错误状态码呢?我们目前是把响应`response`返回了,但并没有对错误状态码做任何处理,所以,从框架的健壮性考虑,我们还需要对错误状态码进行处理。
# 2. 错误处理分类
经过分析,我们认为,对于错误处理,我们大致分为以下几类:
- 请求错误处理:在请求发送过程中出现的错误,如:请求发送超时等;
- 响应错误处理:在响应接收过程中出现的错误,如:服务器返回`400`、`500`等错误状态码;
# 3. 需求分析
我们希望程序能够自动捕获这些错误,并且对外抛出,以供用户对错误进行处理。例如:
```javascript
axios({
method: "get",
url: "/api/handleError",
})
.then((res) => {
console.log(res);
})
.catch((e) => {
console.log(e);
});
```
当请求发送成功并且服务器也成功响应,但是响应的状态码是错误状态码,那么我们希望程序能够自动捕获到该错误,并且抛出一个带有错误信息的`promise`,这样用户就可以在`catch`中捕获到该错误。
# 4. 错误对象创建
首先,我们先来创建一个错误对象,该错误对象上包含了一些错误信息。我们在`src`目录下创建`helpers/error.ts`文件:
```typescript
import { AxiosRequestConfig, AxiosResponse } from "../types";
export class AxiosError extends Error {
isAxiosError: boolean;
config: AxiosRequestConfig;
code?: string | null;
request?: any;
response?: AxiosResponse;
constructor(
message: string,
config: AxiosRequestConfig,
code?: string | null,
request?: any,
response?: AxiosResponse
) {
super(message);
this.config = config;
this.code = code;
this.request = request;
this.response = response;
this.isAxiosError = true;
Object.setPrototypeOf(this, AxiosError.prototype);
}
}
export function createError(
message: string,
config: AxiosRequestConfig,
code?: string | null,
request?: any,
response?: AxiosResponse
) {
const error = new AxiosError(message, config, code, request, response);
return error;
}
```
我们创建了一个`AxiosError`类,它继承于`Error`类,并且我们还给这个类扩展了一些属性:
- `isAxiosError`:标识是否是`axios`错误,因为我们自定义了错误类,为了跟原生的`Error`类区分,我们用该属性来标识;
- `config`:请求配置对象;
- `code`:错误代码;
- `request`:请求对象;
- `response`:响应对象;
另外,为了方便使用,我们还创建了一个`createError`工厂方法。
OK,错误对象创建好之后,接下来我们就来处理错误。
# 5. 处理请求错误
请求错误指的是:在请求发送过程中出现的错误。例如:请求发送超时、网络错误等。
我们在`src/xhr.ts`文件中处理请求错误:
```typescript
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
)
);
}
}
});
}
```
我们给`request`对象注册了`onerror`和`ontimeout`事件。
- 当触发`onerror`事件时,我们调用`reject`抛出错误,错误信息为`"Network Error"`,并且错误代码为`null`;
- 当触发`ontimeout`事件时,我们调用`reject`抛出错误,错误信息为`"Timeout of ${timeout} ms exceeded"`,并且错误代码为`"ECONNABORTED"`;
另外,我们还把之前处理响应数据的逻辑抽离到了`handleResponse`函数中,在该函数中,我们判断如果响应的状态码在`200~300`之间,则表示请求成功,我们调用`resolve(response)`将响应`response`返回,否则,我们调用`reject`抛出错误,错误信息为`"Request failed with status code ${response.status}"`,并且把响应对象`response`也传进去。
# 6. 处理响应错误
响应错误指的是:服务器返回了响应,但是响应的状态码是错误状态码,例如:`400`、`500`等。
其实,对于响应错误的处理,我们在上一步处理请求错误的时候就已经处理了,也就是在`handleResponse`函数中,如果状态码不在`200~300`之间,我们就抛出错误。
# 7. 编写 demo
接下来,我们编写 `demo` 来测试下错误处理是否生效。
在 `examples` 目录下创建 `handleError` 目录,并在该目录下创建 `index.`:
```
< lang="en">
handleError demo
>
```
接着再创建 `app.ts` 作为入口文件:
```typescript
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/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`的入口:
```
handleError
```
OK, 现在我们启动服务:
```bash
# 同时开启客户端和服务端
npm run server | npm start
```
接着打开浏览器,访问: http://localhost:8000/ 然后我们点击 `handleError`,通过`F12`的控制台我们可以看到:所有的请求错误都被捕获到了,并且打印出了错误信息。

# 8. 遗留问题
我们虽然对错误做了处理,但是我们在使用的时候发现,我们在`catch`中捕获的错误`e`是`AxiosError`类型,但是如果我们访问`e.response`的时候,`TypeScript`并不能智能的提示出`AxiosResponse`类型,这是为什么呢?
这是因为`TypeScript`在不知道一个对象是什么类型的情况下,并不知道它是否具有某个属性。例如:
```typescript
const foo = {};
console.log(foo.bar); // Property 'bar' does not exist on type '{}'.
```
所以,我们需要告诉`TypeScript`,`e`是`AxiosError`类型,这样它才能智能的提示出`AxiosResponse`类型。
但是,我们在使用的时候,并不能在`catch`中指定错误类型,因为`catch`的参数类型是`any`,所以我们需要在`catch`中手动判断错误类型。
```typescript
axios({
method: "get",
url: "/error/get1",
})
.then((res) => {
console.log(res);
})
.catch((e) => {
if (e.isAxiosError) {
// 是AxiosError类型
console.log(e.response);
} else {
// 其他错误类型
console.log(e);
}
});
```
这样,我们就可以在`catch`中手动判断错误类型了。
# 9. 总结
至此,我们就实现了`axios`的错误处理功能。我们创建了`AxiosError`类,并且对请求错误和响应错误做了处理,使得框架更加健壮。
另外,我们还遗留了一个问题,就是`TypeScript`并不能智能的提示出`AxiosError`类型,我们需要在`catch`中手动判断错误类型。
最新文章
- 燃油车百年发展史:从机械革命到新能源转型
- V2X通信与ADAS系统:车联网技术重塑未来智能出行
- 汽车与未来出行新趋势
- 智能驾驶与车联网技术引领汽车产业新变革
- 智能座舱与电动化引领未来车联网新趋势
- 高速行驶中汽车引擎轰鸣声震耳欲聋
- 智能驾驶与新能源革命:车联网引领未来出行新趋势
- 宝马驾驶体验如同驾驭未来科技
- 双离合变速箱汽车驾驶体验
- 固态电池与ADAS技术:突破传统汽车电气架构的革命性结合
- 电动化与智能化并行:电池技术、激光雷达与V2X引领汽车产业变革
- 磷酸铁锂电池:新能源汽车安全与经济性的双重突破
- 智能驾驶技术突破:从L3到L4的五年变革之路
- 车载灭火器与ABS系统:行车安全防护全指南
- 智能座舱与自动驾驶:未来汽车技术的五大革新方向
- 轮胎花纹全解析:如何选择与保养提升行车安全
- 可回收电动汽车环保节能未来出行新选择
- 保时捷跑车在赛道上飞驰
- 自动驾驶与新能源革命:重塑未来出行新生态
- 3D点云技术重塑汽车数字建模新纪元
