基于TypeScript封装Axios笔记(三)

2020-08-26 10:00:18 浏览数 (1)

处理请求 header

需求分析

我们上面课遗留了一个问题:

代码语言:javascript复制
1axios({
2 method: 'post',
3  url: '/base/post',
4  data: {
5    a: 1,
6    b: 2
7  }
8})

我们做了请求数据的处理,把 data 转换成了 JSON 字符串,但是数据发送到服务端的时候,服务端并不能正常解析我们发送的数据,因为我们并没有给请求 header 设置正确的 Content-Type。

所以首先我们要支持发送请求的时候,可以支持配置 headers 属性,如下:

代码语言:javascript复制
 1axios({
 2 method: 'post',
 3  url: '/base/post',
 4  headers: {
 5 'content-type': 'application/json;charset=utf-8'
 6  },
 7 data: {
 8 a: 1,
 9    b: 2
10  }
11})

并且在当我们传入的 data 为普通对象的时候,headers 如果没有配置 Content-Type 属性,需要自动设置请求 header 的 Content-Type 字段为:application/json;charset=utf-8

processHeaders 函数实现

根据需求分析,我们要实现一个工具函数,对 request 中的 headers 做一层加工。我们在 helpers 目录新建 headers.ts 文件。

helpers/headers.ts:

代码语言:javascript复制
 1import { isPlainObject } from './util'
 2
 3function normalizeHeaderName (headers: any, normalizedName: string): void {
 4 if (!headers) {
 5 return
 6  }
 7 Object.keys(headers).forEach(name => {
 8 if (name !== normalizedName && name.toUpperCase() === normalizedName.toUpperCase()) {
 9      headers[normalizedName] = headers[name]
10 delete headers[name]
11    }
12  })
13}
14
15export function processHeaders (headers: any, data: any): any {
16  normalizeHeaderName(headers, 'Content-Type')
17
18 if (isPlainObject(data)) {
19 if (headers && !headers['Content-Type']) {
20      headers['Content-Type'] = 'application/json;charset=utf-8'
21    }
22  }
23 return headers
24}

这里有个需要注意的点,因为请求 header 属性是大小写不敏感的,比如我们之前的例子传入 header 的属性名 content-type 就是全小写的,所以我们先要把一些 header 属性名规范化。

实现请求 header 处理逻辑

在这之前,我们先修改一下 AxiosRequestConfig 接口类型的定义,添加 headers 这个可选属性:

types/index.ts

代码语言:javascript复制
1export interface AxiosRequestConfig {
2  url: string
3  method?: Method
4  data?: any
5 params?: any
6  headers?: any
7}

index.ts:

代码语言:javascript复制
 1function processConfig (config: AxiosRequestConfig): void {
 2 config.url = transformURL(config)
 3 config.headers = transformHeaders(config)
 4 config.data = transformRequestData(config)
 5}
 6
 7function transformHeaders (config: AxiosRequestConfig) {
 8  const { headers = {}, data } = config
 9 return processHeaders(headers, data)
10}

因为我们处理 header 的时候依赖了 data,所以要在处理请求 body 数据之前处理请求 header。

xhr.ts:

代码语言:javascript复制
 1export default function xhr (config: AxiosRequestConfig): void {
 2 const { data = null, url, method = 'get', headers } = config
 3
 4 const request = new XMLHttpRequest()
 5
 6  request.open(method.toUpperCase(), url, true)
 7
 8 Object.keys(headers).forEach((name) => {
 9 if (data === null && name.toLowerCase() === 'content-type') {
10 delete headers[name]
11    } else {
12      request.setRequestHeader(name, headers[name])
13    }
14  })
15
16  request.send(data)
17}

这里要额外判断一个逻辑,当我们传入的 data 为空的时候,请求 header 配置 Content-Type 是没有意义的,于是我们把它删除。

demo 编写

代码语言:javascript复制
 1axios({
 2  method: 'post',
 3  url: '/base/post',
 4  data: {
 5    a: 1,
 6    b: 2
 7  }
 8})
 9
10axios({
11  method: 'post',
12  url: '/base/post',
13  headers: {
14 'content-type': 'application/json;'
15  },
16  data: {
17    a: 1,
18    b: 2
19  }
20})
21
22const paramsString = 'q=URLUtils.searchParams&topic=api'
23const searchParams = new URLSearchParams(paramsString)
24
25axios({
26  method: 'post',
27  url: '/base/post',
28  data: searchParams
29})

通过 demo 我们可以看到,当我们请求的数据是普通对象并且没有配置 headers 的时候,会自动为其添加 Content-Type:application/json;charset=utf-8;同时我们发现当 data 是某些类型如 URLSearchParams 的时候,浏览器会自动为请求 header加上合适的 Content-Type。

至此我们对于请求的处理逻辑暂时告一段落‍。处理响应 header。

处理响应 header

需求分析

我们通过 XMLHttpRequest 对象的 getAllResponseHeaders 方法获取到的值是如下一段字符串:

代码语言:javascript复制
1date: Fri, 05 Apr 2019 12:40:49 GMT
2etag: W/"d-Ssxx4FRxEutDLwo2 xkkxKc4y0k"
3connection: keep-alive
4x-powered-by: Express
5content-length: 13
6content-type: application/json; charset=utf-8

每一行都是以回车符和换行符 rn 结束,它们是每个 header 属性的分隔符。对于上面这串字符串,我们希望最终解析成一个对象结构:

代码语言:javascript复制
1{
2 date: 'Fri, 05 Apr 2019 12:40:49 GMT'
3  etag: 'W/"d-Ssxx4FRxEutDLwo2 xkkxKc4y0k"',
4  connection: 'keep-alive',
5 'x-powered-by': 'Express',
6 'content-length': '13'
7 'content-type': 'application/json; charset=utf-8'
8}

parseHeaders 函数实现及应用

根据需求分析,我们要实现一个 parseHeaders 工具函数。

helpers/headers.ts:

代码语言:javascript复制
 1export function parseHeaders(headers: string): any {
 2 let parsed = Object.create(null)
 3 if (!headers) {
 4 return parsed
 5  }
 6
 7  headers.split('rn').forEach(line => {
 8 let [key, ...vals] = line.split(':')
 9    key = key.trim().toLowerCase()
10 if (!key) {
11 return
12    }
13 const val = vals.join(':').trim()
14    parsed[key] = val
15  })
16
17 return parsed
18}

然后我们使用这个工具函数:

xhr.ts:

1const responseHeaders = parseHeaders(request.getAllResponseHeaders())

接着我们再去看刚才的 demo,发现我们已经把响应的 headers 字段从字符串解析成对象结构了。那么接下来,我们在解决之前遗留的第二个问题:对响应 data 字段的处理。

处理响应 data

需求分析

在我们不去设置 responseType 的情况下,当服务端返回给我们的数据是字符串类型,我们可以尝试去把它转换成一个 JSON 对象。例如:

1data: "{"a":1,"b":2}"

我们把它转换成:

代码语言:javascript复制
1data: {
2 a: 1,
3  b: 2
4}

transformResponse 函数实现及应用

根据需求分析,我们要实现一个 transformResponse 工具函数。

helpers/data.ts:

代码语言:javascript复制
 1export function transformResponse(data: any): any {
 2 if (typeof data === 'string') {
 3 try {
 4      data = JSON.parse(data)
 5    } catch (e) {
 6 // do nothing
 7    }
 8  }
 9 return data
10}

index.ts:

代码语言:javascript复制
 1function axios(config: AxiosRequestConfig): AxiosPromise {
 2  processConfig(config)
 3 return xhr(config).then((res) => {
 4 return transformResponseData(res)
 5  })
 6}
 7
 8function transformResponseData(res: AxiosResponse): AxiosResponse {
 9  res.data = transformResponse(res.data)
10 return res
11}

接着我们再去看刚才的 demo,发现我们已经把响应的 data 字段从字符串解析成 JSON 对象结构了。

那么至此,我们的 ts-axios 的基础功能已经实现完毕。不过到目前为止,我们都仅仅实现的是正常情况的逻辑,下面一章我们要处理各种异常情况的逻辑。

0 人点赞