当前位置 : 首页 > 购买指南

《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`的控制台我们可以看到:所有的请求错误都被捕获到了,并且打印出了错误信息。 ![](~@/axios/08/01.png) # 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`中手动判断错误类型。

    栏目列表