# 1. 前言

在之前的文章中,虽然我们已经实现了 axios 的基本功能,但是那些都是正常情况,我们知道,在实际开发中,不可能没有错误发生。所以,接下来我们需要对一些常见的错误情况进行异常捕获和处理。我们期望,当异常出现时,我们都可以在 reject 回调函数中捕获到,如下形式:

axios({
  method: "get",
  url: "/api/handleError",
})
  .then((res) => {
    console.log(res);
  })
  .catch((e) => {
    console.log(e);
  });
1
2
3
4
5
6
7
8
9
10

# 2. 需求分析

首先我们先来分析下一些常见的请求异常:

  • 网络异常,当网络不通时抛出的异常;
  • 请求超时,当请求发出后在指定时间内没有收到响应时抛出的异常。
  • 状态码非 200-300 异常,当请求的状态码不在 200-300 之间时,我们也认为它出现了异常;

OK,接下来,我们就对上面所列出的三种异常进行分别处理。

# 3. 异常处理

# 3.1 网络异常

当网络出现异常(比如不通)的时候发送请求会触发 XMLHttpRequest 对象实例的 error 事件,于是我们就可以在 onerror (opens new window) 的事件回调函数中捕获此类错误。

我们在 src/xhr.ts 中的 xhr 函数中添加如下代码:

// 4.1 网络错误事件
request.onerror = function() {
  reject(new Error("Net Error"));
};
1
2
3
4

# 3.2 请求超时

在官方 axios 中,它允许用户在发请求时配置请求的超时时间 timeout ,也就是当请求发送后超过某个时间后仍然没收到响应,则请求自动终止,并会触发 XMLHttpRequest 对象实例的 ontimeout 事件。

  1. 首先,我们也要允许用户在发请求时可配置超时时间,所以我们在之前写好的请求参数接口类型AxiosRequestConfig里添加timeout选项,如下:
// src/types/index.ts

export interface AxiosRequestConfig {
  url: string;
  method?: Method;
  headers?: any;
  data?: any;
  params?: any;
  responseType?: XMLHttpRequestResponseType;
  timeout?: number;
}
1
2
3
4
5
6
7
8
9
10
11
  1. 接着,在src/xhr.ts中的xhr函数中获取用户配置的timeout, 如果该参数不为空,则将其设置到 XMLHttpRequest 对象实例request上。
const {
  data = null,
  url,
  method = "get",
  headers,
  responseType,
  timeout,
} = config;
if (timeout) {
  request.timeout = timeout;
}
1
2
3
4
5
6
7
8
9
10
11
  1. 最后,我们通过注册 XMLHttpRequest 对象实例的 ontimeout 事件来捕获请求超时异常。
request.ontimeout = function() {
  reject(new Error(`Timeout of ${timeout} ms exceeded`));
};
1
2
3

# 3.3 非 200-300 状态码

对于一个正常的请求,往往会返回 200-300 之间的 HTTP 状态码,对于不在这个区间的状态码,我们也把它们认为是一种错误的情况做处理。

  1. 首先,我们先在xhr函数中的onreadystatechange 的回调函数中,添加了对 request.status (opens new window) 的判断,因为当出现网络错误或者超时错误的时候,该值都为 0。
if (request.status === 0) {
  return;
}
1
2
3
  1. 然后,我们再判断状态码是否在 200-300 之间,来决定是否抛出异常。

我们编写一个辅助函数 handleResponse ,如果状态码 response.status 在 200-300 之间,则 resolve(response) ,否则 reject

function handleResponse(response: AxiosResponse): void {
  if (response.status >= 200 && response.status < 300) {
    resolve(response);
  } else {
    reject(new Error(`Request failed with status code ${response.status}`));
  }
}
1
2
3
4
5
6
7

OK,三种异常处理就已经写完了,接下来我们就可以编写 demo ,来测试下效果如何。

# 4. demo 编写

examples 目录下创建 handleError 目录,在 handleError 目录下创建 index.html :

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>handleError demo</title>
  </head>

  <body>
    <script src="/__build__/handleError.js"></script>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11

接着再创建 app.ts 作为入口文件:

import axios from "../../src/index";

// 1.正常情况
axios({
  method: "get",
  url: "/api/handleError",
})
  .then((res) => {
    console.log(res);
  })
  .catch((e) => {
    console.log(e);
  });

// 2.url故意写错
axios({
  method: "get",
  url: "/api/handleError1",
})
  .then((res) => {
    console.log(res);
  })
  .catch((e) => {
    console.log(e);
  });

// 3. 模拟网络错误
setTimeout(() => {
  axios({
    method: "get",
    url: "/api/handleError",
  })
    .then((res) => {
      console.log(res);
    })
    .catch((e) => {
      console.log(e);
    });
}, 5000);

// // 4.配置请求超时时间为2秒,模拟请求超时
axios({
  method: "get",
  url: "/api/handleError/timeout",
  timeout: 2000,
})
  .then((res) => {
    console.log(res);
  })
  .catch((e) => {
    console.log(e.message);
  });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

接着在 server/server.js 添加新的接口路由:

// 响应正常情况,有50%几率响应成功,有50%几率响应失败,返回状态码500
router.get("/api/handleError", function(req, res) {
  if (Math.random() > 0.5) {
    res.json({
      msg: `hello world`,
    });
  } else {
    res.status(500);
    res.end();
  }
});
// 响应请求超时情况,这里我们设置3秒后响应,而发请求那里设置了超时时间为3秒,所以会发生请求超时异常
router.get("/api/handleError/timeout", function(req, res) {
  setTimeout(() => {
    res.json({
      msg: `hello world`,
    });
  }, 3000);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

最后在根目录下的 index.html 中加上启动该 demo 的入口:

<li><a href="examples/handleError">handleError</a></li>
1

OK, 我们在命令行中执行:

# 同时开启客户端和服务端
npm run server | npm start
1
2

接着我们打开 chrome 浏览器,访问 http://localhost:8000/ (opens new window) 即可访问我们的 demo 了,我们点击 handleError ,通过 F12 的控制台我们可以看到:有 3 条请求的响应信息都已经被打印出来了:

  • 第一条为请求成功状态,返回信息已经被打印出来
  • 第一条请求状态码为 500,成功打印除了状态码信息Error: Request failed with status code 500
  • 第二条我们故意把请求的url写错,所以报了 404 错误,我们也成功打印出了错误信息;
  • 第三条响应超时,也打印出了超时信息Timeout of 2000 ms exceeded

接着,我们刷新浏览器,然后迅速的在 5 秒之内点击 F12network 选项,把 offline 勾选上,此时表示浏览器网络断开,我们就能看到模拟网络错误情况。

# 5. 遗留问题

OK,三种异常情况虽然都已经成功处理了,但是这仅仅是错误的文本信息,对于排错来说极不方便,所以我们最好还能够返回错误属于哪个请求、请求的配置、响应对象等其它信息。那么,下篇文章我们就来继续补充,实现异常处理的增强版。