目录

🌘 实现异常情况处理

# 需求分析

前面实现了请求的基本需求功能。目前为止,都是处理了正常接收请求的逻辑,并没有考虑任何错误情况,所以对于这个程序的健壮性不够,因此需要对 AJAX 的各种情况作处理。

程序能够捕获到这些错误,并且做进一步的处理:

axios({
  method: 'get',
  url: 'error/get'
}).then(res => {
  console.log(res)
}).catch(e => {
  console.log(e)
})
1
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'))
    }
    
    // ... 
}
1
2
3
4
5
6
7
8
9
10

# 处理请求超时错误

可以设置某个请求的超时时间 timeout ,也就是当请求发送后超过某个时间后仍然没有收到响应,则请求自动终止,并且触发 timeout 事件。

请求默认的超时时间为 0,即永不超时;

首先先在 AxiosRequestConfig 类型中添加可选配置字段 timeout

export interface AxiosRequestConfig {
 	timeout?: number
}
1
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`))
    }
    
    // ...
}
1
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}`))
  }
}
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
  • 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>
1
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)
  })
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

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)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

通过开发者工具的 network 部分我们可以看到不同的错误情况。

至此对各种错误都做了处理,并把它们抛给了程序应用方,让他们对错误可以做进一步的处理。

但是这里的错误都仅仅是简单的 Error 实例,只有错误文本信息,并不包含是哪个请求、请求的配置、响应对象等其它信息。需要对错误信息增强;

# 需求分析

抛出的错误不能只是一个简单的文本信息,应该包括请求的对象配置 config 、错误代码 codeXMLHttpRequest 对象实例 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)
})
1
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
}
1
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)
}
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

# 应用方法 createError

修改 scr/xhr.tsxhr 函数中的处理错误逻辑:

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))
}
1
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
1
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)
})
1
2
3
4
5
6
7
8
9
10
📢 上次更新: 2022/09/02, 10:18:16