获取响应数据
需求分析
在前面的章节中,我们发送的请求都可以从网络层面接收到服务端返回的数据,但是代码层面并没有做任何关于返回数据的处理。我们希望能处理服务端响应的数据,并支持 Promise 链式调用的方式,如下:
axios({
method: 'post',
url: '/base/post',
data: {
a: 1,
b: 2
}
}).then((res) => {
console.log(res)
})
axios({
method: 'post',
url: '/base/post',
data: {
a: 1,
b: 2
}
}).then((res) => {
console.log(res)
})
我们可以拿到 res
对象,并且我们希望该对象包括:服务端返回的数据 data
,HTTP 状态码status
,状态消息 statusText
,响应头 headers
、请求配置对象 config
以及请求的 XMLHttpRequest
对象实例 request
。
定义接口类型
根据需求,我们可以定义一个 AxiosResponse
接口类型,如下:
export interface AxiosResponse {
data: any
status: number
statusText: string
headers: any
config: AxiosRequestConfig
request: any
}
export interface AxiosResponse {
data: any
status: number
statusText: string
headers: any
config: AxiosRequestConfig
request: any
}
另外,axios
函数返回的是一个 Promise
对象,我们可以定义一个 AxiosPromise
接口,它继承于 Promise<AxiosResponse>
这个泛型接口:
export interface AxiosPromise extends Promise<AxiosResponse> {
}
export interface AxiosPromise extends Promise<AxiosResponse> {
}
这样的话,当 axios
返回的是 AxiosPromise
类型,那么 resolve
函数中的参数就是一个 AxiosResponse
类型。
对于一个 AJAX 请求的 response
,我们是可以指定它的响应的数据类型的,通过设置 XMLHttpRequest
对象的 responseType
属性,于是我们可以给 AxiosRequestConfig
类型添加一个可选属性:
export interface AxiosRequestConfig {
// ...
responseType?: XMLHttpRequestResponseType
}
export interface AxiosRequestConfig {
// ...
responseType?: XMLHttpRequestResponseType
}
responseType
的类型是一个 XMLHttpRequestResponseType
类型,它的定义是 "" | "arraybuffer" | "blob" | "document" | "json" | "text"
字符串字面量类型。
实现获取响应数据逻辑
首先我们要在 xhr
函数添加 onreadystatechange
事件处理函数,并且让 xhr
函数返回的是 AxiosPromise
类型。
xhr.ts
:
export default function xhr(config: AxiosRequestConfig): AxiosPromise {
return new Promise((resolve) => {
const { data = null, url, method = 'get', headers, responseType } = config
const request = new XMLHttpRequest()
if (responseType) {
request.responseType = responseType
}
request.open(method.toUpperCase(), url, true)
request.onreadystatechange = function handleLoad() {
if (request.readyState !== 4) {
return
}
const responseHeaders = 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
}
resolve(response)
}
Object.keys(headers).forEach((name) => {
if (data === null && name.toLowerCase() === 'content-type') {
delete headers[name]
} else {
request.setRequestHeader(name, headers[name])
}
})
request.send(data)
})
}
export default function xhr(config: AxiosRequestConfig): AxiosPromise {
return new Promise((resolve) => {
const { data = null, url, method = 'get', headers, responseType } = config
const request = new XMLHttpRequest()
if (responseType) {
request.responseType = responseType
}
request.open(method.toUpperCase(), url, true)
request.onreadystatechange = function handleLoad() {
if (request.readyState !== 4) {
return
}
const responseHeaders = 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
}
resolve(response)
}
Object.keys(headers).forEach((name) => {
if (data === null && name.toLowerCase() === 'content-type') {
delete headers[name]
} else {
request.setRequestHeader(name, headers[name])
}
})
request.send(data)
})
}
注意,我们这里还判断了如果 config
中配置了 responseType
,我们把它设置到 request.responseType
中。在 onreadystatechange
事件函数中,我们构造了 AxiosResponse
类型的 reponse
对象,并把它 resolve
出去。
修改了 xhr
函数,我们同样也要对应修改 axios
函数:
index.ts
:
function axios(config: AxiosRequestConfig): AxiosPromise {
processConfig(config)
return xhr(config)
}
function axios(config: AxiosRequestConfig): AxiosPromise {
processConfig(config)
return xhr(config)
}
这样我们就实现了 axios
函数的 Promise 化。
demo 编写
我们在 examples/base/app.ts
文件中添加 2 段代码:
axios({
method: 'post',
url: '/base/post',
data: {
a: 1,
b: 2
}
}).then((res) => {
console.log(res)
})
axios({
method: 'post',
url: '/base/post',
responseType: 'json',
data: {
a: 3,
b: 4
}
}).then((res) => {
console.log(res)
})
axios({
method: 'post',
url: '/base/post',
data: {
a: 1,
b: 2
}
}).then((res) => {
console.log(res)
})
axios({
method: 'post',
url: '/base/post',
responseType: 'json',
data: {
a: 3,
b: 4
}
}).then((res) => {
console.log(res)
})
我们打开浏览器运行 demo,看一下结果,发现我们可以正常 log 出这个 res
变量,它包含 AxiosResponse
类型中定义的那些属性,不过我们发现 2 个小问题:第一个是 headers
属性是一个字符串,我们需要把它解析成对象类型;第二个是在第一个请求中,得到的数据是一个 JSON 字符串,我们也需要把它转换成对象类型。
那么下一小节,我们将来解决第一个问题,对于响应的 header
做处理。