【问题标题】:Javascript: Function that retries with setTimeoutJavascript:使用 setTimeout 重试的函数
【发布时间】:2015-08-10 14:24:50
【问题描述】:

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

这是我目前所拥有的:

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 中运行很重要。

【问题讨论】:

  • 也许使用 promise 不适合你的场景。我不明白为什么人们突然对每个异步案例都使用 Promise。
  • 我不用promise,你有没有promise的解决方案?
  • @webduvet:因为这就是承诺的目的:具有单一结果的异步案例。它确实非常适合这种情况。
  • @AlexD:你的downloadItem函数真的是同步的吗?我认为它应该返回一个承诺。
  • @alexd 与 Bluebird 您可以使用 Promise.delayPromise.try

标签: javascript node.js promise settimeout


【解决方案1】:

我有两个想法:

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

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);
    });
}

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

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);
        }
    });
}

【讨论】:

  • 我真的很喜欢你的第一个解决方案!在声明内部函数之后,我已经对其进行了一些更改,因为您还必须调用它一次。我现在就试试
  • 马虎我 - 忘记运行该功能 - 已修复。
  • @Bergi - 谢谢。没想到落入同一个延迟陷阱。
【解决方案2】:

没有必要创建新的 Promise 来处理这个问题。假设downloadItem 是同步的并返回一个promise,只需返回调用它的结果,以及一个catch 以递归调用downloadItemWithRetryAndTimeout

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)
  );
}

有些人可能会觉得以下内容稍微干净一些:

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

【讨论】:

  • 通过通用重试方法const retry = (fn, retries = 3) =&gt; fn().catch(e =&gt; retries &lt;= 0? Promise.reject(e) : retry(fn, retries - 1)) 并用超时组合它会更有意义:const delay = ms =&gt; new Promise(r =&gt; setTimeout(r, ms))const delayError = (fn, ms) =&gt; fn().catch(e =&gt; delay(ms).then(y =&gt; Promise.reject(e)))。然后代码变成const downloadWithRetryAndTimeout = retry(delayError(download, 1000))或类似的东西。
  • @BenjaminGruenbaum 感谢您的优雅分解。
【解决方案3】:

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

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

实际上应该是:

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

所以它会返回一个函数,而不是一个承诺。这是一个很难解决的棘手错误,所以我在这里发布以防万一有人需要它。以下是全部内容:

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))

【讨论】:

    【解决方案4】:
    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);
        });
    }
    

    【讨论】:

      猜你喜欢
      • 2018-09-22
      • 1970-01-01
      • 2013-08-09
      • 2021-01-22
      • 2011-10-23
      • 2020-12-24
      • 1970-01-01
      • 1970-01-01
      • 2023-03-12
      相关资源
      最近更新 更多