使用promise封装一个接口重试机制

934 阅读2分钟

在请求接口的过程中,我们会遇到接口报错然后重试的场景,接下来我们就封装一个。

先模拟一个报错的接口

// 模拟失败的接口,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 实例,并把该实例的 resolvereject 方法暴露给用户,让用户来决定下一步应该怎么做。这里,我们将新的 Promise 实例的 resolvereject 分别封装为 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)
  })

image.png

总共执行了4次,初始1次,重试3次,最后失败。