🌘 实现异常情况处理
# 需求分析
前面实现了请求的基本需求功能。目前为止,都是处理了正常接收请求的逻辑,并没有考虑任何错误情况,所以对于这个程序的健壮性不够,因此需要对 AJAX 的各种情况作处理。
程序能够捕获到这些错误,并且做进一步的处理:
axios({
method: 'get',
url: 'error/get'
}).then(res => {
console.log(res)
}).catch(e => {
console.log(e)
})
2
3
4
5
6
7
8
如果请求的过程中发生任何处理,都可以在 reject
回调函数中捕获到。
将错误分为几类处理。
# 处理网络异常错误
当网络出现异常时,发送请求会触发 XMLHttpRequest
对象实例中的 error
事件时,所以可以在 onerror
中捕获:
在 src/xhr.ts
中添加:
export default function xhr(config: AxiosRequestConfig): AxiosPromise {
return new Promise((resolve, reject) => {
// ...
request.onerror = function handleError() {
reject(new Error('Network Error'))
}
// ...
}
2
3
4
5
6
7
8
9
10
# 处理请求超时错误
可以设置某个请求的超时时间 timeout
,也就是当请求发送后超过某个时间后仍然没有收到响应,则请求自动终止,并且触发 timeout
事件。
请求默认的超时时间为 0,即永不超时;
首先先在 AxiosRequestConfig
类型中添加可选配置字段 timeout
:
export interface AxiosRequestConfig {
timeout?: number
}
2
3
然后在 src/xhr.ts
中添加:
export default function xhr(config: AxiosRequestConfig): AxiosPromise {
return new Promise((resolve, reject) => {
const { data = null, url, method = 'get', headers, responseType, timeout } = config
// ...
if (timeout) {
request.timeout = timeout
}
//...
request.ontimeout = function handleTimeout() {
reject(new Error(`Timeout of ${timeout} ms exceeded`))
}
// ...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 处理非 200
状态码
对于一个正常的请求,往往会返回 200-300 之间的 HTTP 状态码,对于不在这个区间的状态码,也把它们认为是一种错误的情况做处理。
在 src/xhr.ts
函数中添加 状态码的处理:
request.onreadystatechange = function handleLoad() {
if(request.readyState !== 4) {
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)
}
function handleResponse(response: AxiosResponse) {
if(response.status >= 200 && response.status < 300) {
resolve(response)
} else {
reject(new Error(`Request failed with status code ${response.status}`))
}
}
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
在
onreadystatechange
的回调函数中,添加了对request.status
的判断,因为当出现网络错误或者超时错误的时候,该值都为 0。在
handleResponse
函数中对request.status
的值再次判断,如果是2xx
的状态码,则认为是一个正常的请求,否则抛错;
# 编写测试 DEMO
在 examples
目录下创建 error
目录:
example/error/index.html
:
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'>
<title>base example</title>
</head>
<body>
<script src='/__build__/base.js'></script>
</body>
</html>
2
3
4
5
6
7
8
9
10
examples/erorr/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)
})
setTimeout(() => {
axios({
method: 'get',
url: '/error/get'
})
.then(res => {
console.log(res)
})
.catch(e => {
console.log(e)
})
}, 5000)
axios({
method: 'get',
url: '/error/timeout',
timeout: 2000
})
.then(res => {
console.log(res)
})
.catch(e => {
console.log(e.message)
})
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
在 examples/server.js
中添加 router
:
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)
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
通过开发者工具的 network 部分我们可以看到不同的错误情况。
至此对各种错误都做了处理,并把它们抛给了程序应用方,让他们对错误可以做进一步的处理。
但是这里的错误都仅仅是简单的 Error 实例,只有错误文本信息,并不包含是哪个请求、请求的配置、响应对象等其它信息。需要对错误信息增强;
# 需求分析
抛出的错误不能只是一个简单的文本信息,应该包括请求的对象配置 config
、错误代码 code
、 XMLHttpRequest
对象实例 request
以及自定义对象 response
。如下:
axios({
method: 'get',
url: '/error/timeout',
timeout: 2000
}).then((res) => {
console.log(res)
}).catch((e: AxiosError) => {
console.log(e.message)
console.log(e.request)
console.log(e.code)
})
2
3
4
5
6
7
8
9
10
11
这样对于应用方来说,他们就可以捕获到这些错误的详细信息,做进一步的处理。
# 创建 AxiosError
类型接口
在 src/type/index.ts
中添加 AxiosError
类型接口:
export interface AxiosError extends Error {
config: AxiosRequestConfig
code?: number
request?: any
response?: AxiosResponse
isAxiosError: Boolean
}
2
3
4
5
6
7
然后创建 src/helpers/erorr.ts
文件,创建 AxiosError
类,继承于 Error
:
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.isAxiosError = true
this.config = config
this.code = code
this.request = request
this.response = response
Object.setPrototypeOf(this, AxiosError.prototype)
}
}
export function createError(
message: string,
config: AxiosRequestConfig,
code?: string | null,
request?: any,
response?: AxiosResponse
): AxiosError {
return new AxiosError(message, config, code, request, response)
}
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
- 使用
Object.setPrototypeOf(this, AxiosError.prototype)
解决 TypeScript 继承内置对象时可能遇到的问题。TypeScript-wiki/Breaking-Changes.md at main · microsoft/TypeScript-wiki (github.com) (opens new window)AxiosError
继承于Error
类,添加了一些自己的属性:config
、code
、request
、response
、isAxiosError
等属性。- 为了方便使用,对外暴露一个
createError
方法创建AxiosError
类的实例。
# 应用方法 createError
修改 scr/xhr.ts
中 xhr
函数中的处理错误逻辑:
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 = function handleError() {
reject(createError('Network Error', config, null, request))
}
request.ontimeout = function handleTimeout() {
reject(createError(`Timeout of ${timeout} ms exceeded`, config, 'ECONNABORTED', request))
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 导出类型定义
由于在 DEMO 中, TypeScript 不能把 e
参数推断为 AxiosError
类型,需要手动知名类型,为了让外部应用能引入 AxiosError
类型,需要将它们导出。
创建 axios.ts
文件,把之前 index.ts
的代码拷贝过去,作为 aixos
的主要逻辑;修改 index.ts
为导出文件:
src/index.ts
:
import axios from './axios'
export * from './types'
export default axios
2
3
4
5
这样在 DEMO 中就可以引用 AxiosError
类型了。
如下, examples/error/app.ts
:
import axios, { AxiosError } from '../../src/index'
axios({
method: 'get',
url: '/error/get1'
}).then(res => {
console.log(res)
}).catch((e: AxiosError) => {
console.log(e)
})
2
3
4
5
6
7
8
9
10