需求分析
我们上面课遗留了一个问题:
代码语言: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 的基础功能已经实现完毕。不过到目前为止,我们都仅仅实现的是正常情况的逻辑,下面一章我们要处理各种异常情况的逻辑。