Javascript:使用setTimeout重试的函数

Javascript:使用setTimeout重试的函数,javascript,node.js,promise,settimeout,Javascript,Node.js,Promise,Settimeout,我有一个函数downloadItem,由于网络原因可能会失败,我希望能够在实际拒绝该项之前重试几次。重试需要超时,因为如果存在网络问题,则没有必要立即重试 以下是我到目前为止的情况: function downloadItemWithRetryAndTimeout(url, retry, failedReason) { return new Promise(function(resolve, reject) { try { if (retry &l

我有一个函数
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(url、重试、失败原因){
返回新承诺(功能(解决、拒绝){
试一试{
如果(重试<0&&failedReason!=null)拒绝(failedReason);
下载项目(url);
解决();
}捕获(e){
setTimeout(函数(){
downloadItemWithRetryAndTimeout(url,重试-1,e);
}, 1000);
}
});
}
显然,这将失败,因为在我第二次调用
DownloadItemWithRetry和Timeout时,我没有按要求返回承诺

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

另外,如果代码在NodeJS中运行很重要。

我有两个想法:

将迭代函数downloadItemWithRetryAndTimeout的promise移出,此时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);
    });
}
函数下载包装器(url,重试){
返回新承诺(功能(解决、拒绝){
函数downloadItemWithRetryAndTimeout(url、重试、失败原因){
试一试{
如果(重试<0&&failedReason!=null)
拒绝(失败原因);
下载项目(url);
解决();
}捕获(e){
setTimeout(函数(){
downloadItemWithRetryAndTimeout(url,重试-1,e);
}, 1000);
}
}
downloadItemWithRetryAndTimeout(url,重试,null);
});
}
此解决方案可行,但它是一种反模式,因为它打破了承诺链: 由于每次迭代都会返回一个承诺,所以只需解析该承诺,然后使用。然后解析之前的承诺,依此类推:

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);
        }
    });
}
函数downloadItemWithRetryAndTimeout(url、重试、失败原因){
返回新承诺(功能(解决、拒绝){
试一试{
如果(重试<0&&failedReason!=null)
拒绝(失败原因);
下载项目(url);
解决();
}捕获(e){
setTimeout(函数(){
downloadItemWithRetryAndTimeout(url,重试-1,e)。然后(函数(){
解决();
});
}, 1000);
}
});
}

无需创建新的承诺来处理此问题。假设
downloadItem
是同步的并返回一个承诺,只需返回调用它的结果,以及一个
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 wait(n){返回新承诺(resolve=>setTimeout(resolve,n));}
函数downloadItemWithRetryAndTimeout(url,重试){
if(重试<0)返回Promise.reject();
返回下载项(url)。
捕获(()=>等待(1000)。
然后(()=>downloadItemWithRetryAndTimeout(url,重试-1)
);
}
有些人可能会发现以下情况稍微好一些:

function downloadItemWithRetryAndTimeout(url, retry) {
  return function download() {
    return --retry < 0 ? Promise.reject() :
      downloadItem(url) . catch(() => wait(1000) . then(download));
  }();
}
函数downloadItemWithRetryAndTimeout(url,重试){
返回函数下载(){
return--重试<0?承诺.拒绝():
下载项目(url).catch(()=>wait(1000).then(下载));
}();
}

@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))
const retry=(fn,retries=3)=>fn()
const delayError=(fn,ms)=>()=>fn().catch(e=>delay(ms).然后(y=>Promise.reject(e)))
重试(延迟错误(下载,1000))

也许使用承诺不适合你的场景。我不明白为什么人们突然对每一个异步案例使用承诺。我不必使用承诺,你有没有没有没有承诺的解决方案?@webduvet:因为这就是承诺的目的:只有一个结果的异步案例。它非常适合这个场景。@AlexD:你的
downloadItem
函数是否真的同步?我认为它应该返回一个承诺。@alexd with Bluebird您可以使用,我真的很喜欢您的第一个解决方案!在声明内部函数后,我在承诺中对它做了一些更改,您也必须调用它一次。我现在就试试。邋遢的我-忘记运行函数-修复。请避免@Bergi-谢谢。我不认为它会落入同一个延迟陷阱。通过一个通用的重试方法来做这件事会更有意义。
const retry=(fn,retries=3)=>fn().catch(e=>retries new Promise(r=>setTimeout(r,ms))
const delayError=(fn,ms)=>fn().catch(e=>delay(ms)。然后(y=>Promise.reject(e)))
。然后代码变成
const downloadWithRetryAndTimeout=retry(delayError(download,1000))
或类似的东西。@BenjaminGruenbaum感谢这个优雅的分解。
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))