关于 Promise 的重试

ErioifpudErioifpud
1 min read

这段时间经常用到重试功能,因为一些需求实现起来需要频繁访问接口,但这个接口不是我们自己的,服务器在海外,所以访问起来失败率肉眼可见地高,所以我需要在访问失败(因为连接原因)时自动进行重试。

既然常用,那就把他记下来,方便下次查阅。

实现

先准备一个假的 fetch,在文章里面我不会真去访问这个接口,就拿假 fetch 来当例子:

const fetch = (url) => {
  return new Promise((resolve, reject) => {
    const r = Math.random()
    const success = r > 0.5
    if (success) {
      resolve(r)
    } else {
      console.log(`${url}: ${r}`)
      reject(new Error('连接错误'))
    }
  })
}

每次调用 fetch 都有 50% 的几率会失败,就把他当作连接失败吧。

接着是对 fetch 的包装,fetch 中是没有重试功能的,所以要做一个 wrapper 函数去调控他,判断请求是否成功,不成功就走重试逻辑:

function fetchWithRetry(url, retries = 3, delay = 3000) {
  return new Promise((resolve, reject) => {
    const wrapper = (n) => {
      fetch(url)
        .then(response => {
            // 这里处理一些非连接原因的错误
          // if (!response.ok) {
          //   throw new Error('Fetch error');
          // }
          resolve(response);
        })
        .catch(error => {
          if (n > 0) {
            setTimeout(() => wrapper(n - 1), delay);
          } else {
            reject(error);
          }
        });
    };

    wrapper(retries);
  });
}

可以看到 fetchWithRetry 又给包了一层 Promisefetchwrapper 函数中,参数 n 表示剩余重试的次数。

先调用 wrapper 发起一次请求,然后当请求成功时正常 resolve 响应数据,请求出错的时候先判断还有没有重试次数,有就重新调用 wrapper 再次请求。

如果有一系列请求需要重试的话,使用 Promise.allSettled 就好了,相比于 Promise.allallSettled 会在所有 Promise 完成(无论失败与否)后变成 fulfilled,他没有 rejected 状态,可以在 results 中判断是哪个请求失败了。

const urls = ['<https://example1.com>', '<https://example2.org>', '<https://example3.net>'];

Promise.allSettled(urls.map(url => fetchWithRetry(url)))
  .then(results => {
    results.forEach((result, i) => {
      if (result.status === 'fulfilled') {
        console.log(`Request to ${urls[i]} succeeded`);
      } else {
        console.log(`Request to ${urls[i]} failed with ${result.reason}`);
      }
    });
  });
0
Subscribe to my newsletter

Read articles from Erioifpud directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Erioifpud
Erioifpud