手写Axios(上)

1,348 阅读2分钟

阅读建议

如果你对XMLHttpRequest的使用不熟悉,建议先在MDN上先行学习。

项目源码已经上传到github,并已添加注释,您可直接查看源码学习。

文章已经同步到我的技术博客,欢迎交流。

先来看axios的使用方式

axios({
  method: 'post',
  url: '/url',
  data: {
    a: 1,
    b: 2
  }
}).then((res) => {
  console.log(res)
})

axios核心

XMLHttpRequest:具体使用参见XMLHttpRequest

// xhr.js
const request = new XMLHttpRequest()
request.open(method.toUpperCase(), url, true)
request.onreadystatechange = function(){
  // 监听xhr状态改变
}
request.send(data)

数据处理

处理url

  1. 从config中获取到url和params
  2. 如果没有params选项,直接返回url
  3. 如果包含params,对params的key做遍历。如果key为空,忽略该params,如果key不为空,针对value的不同类型做对应处理。具体代码如下:
export function buildUrl(url: string, params: any): string {
  if (!params) return url;

  const parts: string[] = [];
  Object.keys(params).forEach((key) => {
    const val = params[key];
    if (val === null || typeof val === 'undefined') {
      return ''
    }
    let values = [];
    if (Array.isArray(val)) {
      values = val;
      key += '[]'
    } else {
      values = [val]
    }
    values.forEach((val) => {
      if (isDate(val)) {
        val = val.toISOString()
      } else if (isPlainObject(val)) {
        val = JSON.stringify(val)
      }
      parts.push(`${encode(key)}=${encode(val)}`)
    })
  })

  let serializedParams = parts.join('&');
  if (serializedParams) {
    const markIndex = url.indexOf('#')
    if (markIndex !== -1) {
      url = url.slice(0, markIndex)
    }
    url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams
  }
  return url
}

处理获取到的data信息

处理data信息比较简单,直接上代码:

export function transformRequest(data: any): any {
  if (isPlainObject(data)) {
    return JSON.stringify(data)
  }
  return data
}

处理头部信息

  1. 考虑兼容Content-Type大小写不同的情况,利用normalizeHeaderName函数进行规范化
  2. 如果data是对象,并且不存在Content-Type,给其配置默认值为'application/json;charset=utf-8',具体代码如下:
function normalizeHeaderName(headers: any, normalizedName: string): void {
  if (!headers) return
  Object.keys(headers).forEach(name => {
    if (name !== normalizedName && name.toUpperCase() === normalizedName.toUpperCase()) {
      headers[normalizedName] = headers[name]
      delete headers[name]
    }
  })
}

export function processHeaders(headers: any, data: any): any {
  normalizeHeaderName(headers, 'Content-Type')
  if (isPlainObject(data)) {
    if (headers && !headers['Content-Type']) {
      headers['Content-Type'] = 'application/json;charset=utf-8'
    }
  }
  return headers
}

处理返回信息

export function transformResponse(data: any): any {
  if (typeof data === 'string') {
    try {
      data = JSON.parse(data)
    } catch (e) {
      // do nothing
    }
  }
  return data
}

Promise化

  1. 处理xhr.js,把xhr核心包含在promise里面
  2. 当触发onreadystatechange事件的时候,格式化响应数据,并对状态进行判断,分别触发 resolve/reject事件