在请求接口的过程中,我们会遇到接口报错然后重试的场景,接下来我们就封装一个。
先模拟一个报错的接口
// 模拟失败的接口,1s后失败
function fetch() {
return new Promise((resolve, reject) => {
console.log('执行了')
setTimeout(() => {
reject('出错了')
}, 1000)
})
}
代码比较简单,返回一个promise,在内部使用定时器,1秒后reject
封装一个load函数
function load(fetch, onError) {
const p = fetch() // 调用fetch,得到一个promise
return p.catch((err) => { // 捕获错误(未出错时不走这里,就像直接调用fetch一样)
return new Promise((resolve, reject) => { // 重新返回一个promise
const retry = () => resolve(load(fetch, onError)) // 调用retry时resolve一个新的load()执行结果
const fail = () => reject(err)
onError(retry, fail) // 调用回调函数,将resolve、reject的执行权交给外部
})
})
}
load 函数内部调用了 fetch 函数来发送请求,并得到一个 Promise 实例。接着,添加 catch 语句块来捕获该实例的错误。当捕获到错误时,我们有两种选择:要么抛出错误,要么返回一个新的 Promise 实例,并把该实例的 resolve 和 reject 方法暴露给用户,让用户来决定下一步应该怎么做。这里,我们将新的 Promise 实例的 resolve 和 reject 分别封装为 retry 函数和 fail 函数,并将它们作为 onError 回调函数的参数。这样,用户就可以在错误发生时主动选择重试或直接抛出错误。
使用示例:
load((retry, fail) => {
// 在错误发生时主动选择重试或直接抛出错误
retry() // 或 fail()
}
).then((res) => {
console.log(res)
}).catch((err) => {
console.log(err)
})
在上面代码中,如果我们调用了retry(),只有接口出错就会一直重试下去,直到成功,永远不会走到catch;如果我们调用了fail(),只要接口出错就立即抛出错误,就和正常使用接口没区别。
封装成函数,可以指定重试次数
function request(fetch, attempts) {
let retries = 0 // 重试次数
function load(fetch, onError) {
const p = fetch()
return p.catch((err) => {
return new Promise((resolve, reject) => {
const retry = () => {
resolve(load(fetch, onError))
retries++ // 每次重试加1
}
const fail = () => reject(err)
onError(retry, fail, retries) // 同时将重试次数抛出
})
})
}
return load(fetch, (retry, fail, retries) => {
// 重试次数达到指定值
if (retries === attempts) {
fail()
} else {
// 否则重试
retry()
}
})
}
使用示例:
request(fetch, 3) // 第二个参数指定重试次数
.then((res) => {
console.log(res)
})
.catch((e) => {
console.log(e)
})
总共执行了4次,初始1次,重试3次,最后失败。