ajax封装及错误重发 | 面试题

760 阅读3分钟

前言

在面经里经常刷到ajax和错误重发的问题,当时觉得挺简单的就没细想,真到面试被问到的时候发现好多地方考虑不周,面试结束后抽空尽可能完整地实现了这方面的功能。

本文给出4个函数,依次实现 xhr封装、xhr请求重发、Promise封装xhr、fetch请求重发

xhr封装

先上代码吧,尽可能完整地实现了ajax的功能,并返回一个可以中断请求的函数

没啥技术含量,就绑定一下超时时间、请求头、请求事件

const noop = () => {}
let defaults = {
  type: 'GET', // 请求类型
  async: true, // 是否异步执行
  headers: {}, // 请求头信息
  timeout: 0, // 超时时间
  data: null, // 传递数据
  readystatechange: noop, //  readyState 属性发生变化时执行事件
  abort: noop, // 请求停止时执行事件
  error: noop, // 请求失败后执行事件
  load: noop, // 请求完成后执行事件
  loadstart: noop, // 接收到响应数据时执行事件
  loadend: noop, // 请求结束时执行事件
  progress: noop, // 请求接收到更多数据时执行事件
  overtime: noop, // 请求超时执行事件
}

// 封装xhr请求,参数说明见defaults对象
function ajax(url, options) {
  const config = Object.assign({}, defaults, options)
  const xhr = new XMLHttpRequest()
  xhr.open(config.type, url, config.async)
  xhr.timeout = config.timeout
  // 设置请求头
  for (const key in config.headers) {
    xhr.setRequestHeader(key, config.headers[key])
  }
  // 注册事件
  const events = ['readystatechange', 'abort', 'error', 'load', 'loadstart', 'loadend', 'progress', 'overtime']
  for (const event of events) {
    xhr.addEventListener(event, () => {
      config[event](xhr)
    })
  }
  xhr.send(config.data)

  // 返回一个终止请求的函数
  return function () {
    xhr.abort()
  }
}

xhr请求重发

想要请求重发,首先得有判断请求是否失败的条件,我们默认使用状态码

我们使用 loadend 事件,该事件在请求结束时必定执行,无论请求成功,失败,还是压根就没发出去(比如跨域)

如果此时拥有重发次数,就再次执行该函数。注意下一次请求结束时仍会执行本次请求的 loadend 事件,所以要将 count 设置成零避免下次失败一下重发多次请求,也可以选择移除我们添加的结束事件(options.loadend = preLoadend)

// 默认的检测成功请求的函数
const checkSuccess = (res) => (res.status >= 200 && res.status < 300) || res.status === 304
// 自动重发
function autoResend(url, options = {}, count = 0, isSuccess = checkSuccess) {
  // 通过结束事件检测是否成功,默认依照状态码判断
  const preLoadend = options.loadend
  options.loadend = (xhr) => {
    if (preLoadend) {
      preLoadend(xhr)
    }
    if (!isSuccess(xhr) && count > 0) {
      // 重发请求,同时更新中断函数
      abort = autoResend(url, options, count - 1, isSuccess)
      count = 0 // 数字置0,避免重发
    }
  }
  let abort
  abort = ajax(url, options)
  // 返回一个终止请求的函数
  return () => {
    count = 0 // 数字置0,避免重发
    abort()
  }
}

在我们的重发逻辑中,定义的事件函数会多次执行

Promise封装xhr

promise封装很简单,在请求结束时返回 xhr 就行了

使用promise时,一般也不会绑定事件函数,loadend 直接设置就好

// 使用promise时,一般不会绑定事件函数
// 简单的promise封装,一定会返回解决的promise
function ajaxWithPromise(url, options = {}) {
  return new Promise((resolve, reject) => {
    options.loadend = (xhr) => {
      resolve(xhr)
    }
    ajax(url, options)
  })
}

fetch请求重发

很简单,请求成功就直接返回结果,请求失败就递归调用

注意请求异常时(比如跨域),返回一个失败的 promise

// 封装fetch重发功能
function autoResendFetch(url, config, count = 0, isSuccess = checkSuccess) {
  return fetch(url, config).then(
    (response) => {
      if (count > 0 && !isSuccess(response)) {
        return autoResendFetch(url, config, count - 1, isSuccess)
      } else {
        return response
      }
    },
    (err) => {
      if (count > 0) {
        return autoResendFetch(url, config, count - 1, isSuccess)
      } else {
        return Promise.reject(err)
      }
    }
  )
}

结语

以上就是ajax封装及错误重发相关的功能函数了,完整代码可以访问我的 github

其中也搭建了一个简单服务器来测试函数的功能,并提供了 TS 版本的代码