前言
在面经里经常刷到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 版本的代码