获取响应数据

需求分析

在前面的章节中,我们发送的请求都可以从网络层面接收到服务端返回的数据,但是代码层面并没有做任何关于返回数据的处理。我们希望能处理服务端响应的数据,并支持 Promise 链式调用的方式,如下:

  1. axios({
  2. method: 'post',
  3. url: '/base/post',
  4. data: {
  5. a: 1,
  6. b: 2
  7. }
  8. }).then((res) => {
  9. console.log(res)
  10. })

我们可以拿到 res 对象,并且我们希望该对象包括:服务端返回的数据 data,HTTP 状态码status,状态消息 statusText,响应头 headers、请求配置对象 config 以及请求的 XMLHttpRequest 对象实例 request

定义接口类型

根据需求,我们可以定义一个 AxiosResponse 接口类型,如下:

  1. export interface AxiosResponse {
  2. data: any
  3. status: number
  4. statusText: string
  5. headers: any
  6. config: AxiosRequestConfig
  7. request: any
  8. }

另外,axios 函数返回的是一个 Promise 对象,我们可以定义一个 AxiosPromise 接口,它继承于 Promise<AxiosResponse> 这个泛型接口:

  1. export interface AxiosPromise extends Promise<AxiosResponse> {
  2. }

这样的话,当 axios 返回的是 AxiosPromise 类型,那么 resolve 函数中的参数就是一个 AxiosResponse 类型。

对于一个 AJAX 请求的 response,我们是可以指定它的响应的数据类型的,通过设置 XMLHttpRequest 对象的 responseType 属性,于是我们可以给 AxiosRequestConfig 类型添加一个可选属性:

  1. export interface AxiosRequestConfig {
  2. // ...
  3. responseType?: XMLHttpRequestResponseType
  4. }

responseType 的类型是一个 XMLHttpRequestResponseType 类型,它的定义是 "" | "arraybuffer" | "blob" | "document" | "json" | "text" 字符串字面量类型。

实现获取响应数据逻辑

首先我们要在 xhr 函数添加 onreadystatechange 事件处理函数,并且让 xhr 函数返回的是 AxiosPromise 类型。

xhr.ts

  1. export default function xhr(config: AxiosRequestConfig): AxiosPromise {
  2. return new Promise((resolve) => {
  3. const { data = null, url, method = 'get', headers, responseType } = config
  4. const request = new XMLHttpRequest()
  5. if (responseType) {
  6. request.responseType = responseType
  7. }
  8. request.open(method.toUpperCase(), url, true)
  9. request.onreadystatechange = function handleLoad() {
  10. if (request.readyState !== 4) {
  11. return
  12. }
  13. const responseHeaders = request.getAllResponseHeaders()
  14. const responseData = responseType && responseType !== 'text' ? request.response : request.responseText
  15. const response: AxiosResponse = {
  16. data: responseData,
  17. status: request.status,
  18. statusText: request.statusText,
  19. headers: responseHeaders,
  20. config,
  21. request
  22. }
  23. resolve(response)
  24. }
  25. Object.keys(headers).forEach((name) => {
  26. if (data === null && name.toLowerCase() === 'content-type') {
  27. delete headers[name]
  28. } else {
  29. request.setRequestHeader(name, headers[name])
  30. }
  31. })
  32. request.send(data)
  33. })
  34. }

注意,我们这里还判断了如果 config 中配置了 responseType,我们把它设置到 request.responseType 中。在 onreadystatechange 事件函数中,我们构造了 AxiosResponse 类型的 reponse 对象,并把它 resolve 出去。

修改了 xhr 函数,我们同样也要对应修改 axios 函数:

index.ts

  1. function axios(config: AxiosRequestConfig): AxiosPromise {
  2. processConfig(config)
  3. return xhr(config)
  4. }

这样我们就实现了 axios 函数的 Promise 化。

demo 编写

我们在 examples/base/app.ts 文件中添加 2 段代码:

  1. axios({
  2. method: 'post',
  3. url: '/base/post',
  4. data: {
  5. a: 1,
  6. b: 2
  7. }
  8. }).then((res) => {
  9. console.log(res)
  10. })
  11. axios({
  12. method: 'post',
  13. url: '/base/post',
  14. responseType: 'json',
  15. data: {
  16. a: 3,
  17. b: 4
  18. }
  19. }).then((res) => {
  20. console.log(res)
  21. })

我们打开浏览器运行 demo,看一下结果,发现我们可以正常 log 出这个 res 变量,它包含 AxiosResponse 类型中定义的那些属性,不过我们发现 2 个小问题:第一个是 headers 属性是一个字符串,我们需要把它解析成对象类型;第二个是在第一个请求中,得到的数据是一个 JSON 字符串,我们也需要把它转换成对象类型。

那么下一小节,我们将来解决第一个问题,对于响应的 header 做处理。