关于node.js:Javascript:使用setTimeout重试的函数

Javascript: Function that retries with setTimeout

我有一个downloadItem函数,可能由于网络原因而失败,我希望能够在实际拒绝该项目之前重试几次。 重试必须有超时,因为如果存在网络问题,则没有必要立即重试。

这是我到目前为止的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function downloadItemWithRetryAndTimeout(url, retry, failedReason) {
    return new Promise(function(resolve, reject) {
        try {
            if (retry < 0 && failedReason != null) reject(failedReason);

            downloadItem(url);
            resolve();
        } catch (e) {
            setTimeout(function() {
                downloadItemWithRetryAndTimeout(url, retry - 1, e);
            }, 1000);
        }
    });
}

显然,这将失败,因为第二次(及以后)我打电话给downloadItemWithRetryAndTimeout,我没有按要求返回承诺。

我如何使它与第二个承诺一起正常工作?

附言 以防代码在NodeJS中运行。


无需创建新的承诺来处理此问题。假设downloadItem是同步的并返回一个Promise,只需返回调用它的结果以及一个catch来递归调用downloadItemWithRetryAndTimeout

1
2
3
4
5
6
7
8
9
10
function wait(n) { return new Promise(resolve => setTimeout(resolve, n)); }

function downloadItemWithRetryAndTimeout(url, retry) {
  if (retry < 0) return Promise.reject();

  return downloadItem(url) .
    catch(() => wait(1000) .
      then(() => downloadItemWithRetryAndTimeout(url, retry - 1)
  );
}

有些人可能会发现以下稍微干净的东西:

1
2
3
4
5
6
function downloadItemWithRetryAndTimeout(url, retry) {
  return function download() {
    return --retry < 0 ? Promise.reject() :
      downloadItem(url) . catch(() => wait(1000) . then(download));
  }();
}


我有两个想法:

将promise移出迭代函数downloadItemWithRetryAndTimeout-现在resolve()将可用于所有迭代:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function downloadWrapper(url, retry) {
    return new Promise(function (resolve, reject) {
        function downloadItemWithRetryAndTimeout(url, retry, failedReason) {

            try {
                if (retry < 0 && failedReason != null)
                    reject(failedReason);

                downloadItem(url);
                resolve();
            } catch (e) {
                setTimeout(function () {
                    downloadItemWithRetryAndTimeout(url, retry - 1, e);
                }, 1000);
            }

        }

        downloadItemWithRetryAndTimeout(url, retry, null);
    });
}

此解决方案有效,但由于它破坏了承诺链,因此是一种反模式:
每次迭代都返回一个承诺时,只需解决该承诺,然后使用.then来解决先前的承诺,依此类推:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function downloadItemWithRetryAndTimeout(url, retry, failedReason) {
    return new Promise(function (resolve, reject) {
        try {
            if (retry < 0 && failedReason != null)
                reject(failedReason);

            downloadItem(url);
            resolve();
        } catch (e) {
            setTimeout(function () {
                downloadItemWithRetryAndTimeout(url, retry - 1, e).then(function () {
                    resolve();
                });
            }, 1000);
        }
    });
}


@ user663031上的@BenjaminGruenbaum评论很棒,但是有一个小错误,因为:

1
const delayError = (fn, ms) => fn().catch(e => delay(ms).then(y => Promise.reject(e)))

实际上应该是:

1
const delayError = (fn, ms) => () => fn().catch(e => delay(ms).then(y => Promise.reject(e)))

因此它将返回一个函数,而不是一个promise。这是一个棘手的错误,很难解决,所以我在这里张贴,以防有人需要。整个过程如下:

1
2
3
4
const retry = (fn, retries = 3) => fn().catch(e => retries <= 0 ? Promise.reject(e) : retry(fn, retries - 1))
const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
const delayError = (fn, ms) => () => fn().catch(e => delay(ms).then(y => Promise.reject(e)))
retry(delayError(download, 1000))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function downloadItemWithRetryAndTimeout(url, retry) {
    return new Promise(function(resolve, reject) {
        var tryDownload = function(attempts) {
            try {
                downloadItem(url);
                resolve();
            } catch (e) {
                if (attempts == 0)  {
                    reject(e);
                } else {
                    setTimeout(function() {
                        tryDownload(attempts - 1);
                    }, 1000);
                }
            }
        };
        tryDownload(retry);
    });
}