【问题标题】:Promises: Repeat operation until it succeeds?Promises:重复操作直到成功?
【发布时间】:2014-12-28 22:36:30
【问题描述】:

我想重复执行一个操作,每次操作之间的超时时间会增加,直到它成功或经过一定的时间。如何在 Q 中使用 Promise 构建它?

【问题讨论】:

标签: javascript promise q


【解决方案1】:

在我看来,这里的所有答案都非常复杂。 Kos 的想法是正确的,但您可以通过编写更惯用的 promise 代码来缩短代码:

function retry(operation, delay) {
    return operation().catch(function(reason) {
        return Q.delay(delay).then(retry.bind(null, operation, delay * 2));
    });
}

还有 cmets:

function retry(operation, delay) {
    return operation(). // run the operation
        catch(function(reason) { // if it fails
            return Q.delay(delay). // delay 
               // retry with more time
               then(retry.bind(null, operation, delay * 2)); 
        });
}

如果你想在一定时间后超时(比如说 10 秒,你可以简单地这样做:

var promise = retry(operation, 1000).timeout(10000);

该功能已内置于 Q 中,无需重新发明 :)

【讨论】:

  • 这是一个非常好的建议。但是,我发现它有一个问题。即使超时到期,重试函数也会一直调用自己,直到成功,这显然可以是无限的。是否有一种干净的方法可以在超时发生时打破承诺链,以便不再调用处理程序,或者我们是否坚持必须设置标志,甚至放弃 timeout() 以支持每次调用时递增的 nrAttempts 计数器operation() 并检查了一些限制?
  • @JHH 如果你让我使用 bluebird 我会使用取消 - 否则你会使用特殊的闭包变量来模拟它 - 或者将“令牌”(变量)传递给重试并设置它为假。所以 Q 有很多方法,但没有真正干净的方法。
  • 当然有办法。我用一个 maxAttempts 和一个计数器很容易地解决了这个问题,并且完全跳过了使用超时。我更担心的是上面的答案在这方面存在缺陷,如果不小心,人们最终可能会产生无限的操作,随着时间的推移会消耗大量的 CPU。
  • 老实说 - 超时很好 - 问题是首先使用异常进行流量控制 - 不使用异常进行流量控制而是返回结果更有意义 - 如果该结果不成功然后终止操作 - 这样超时可以让您轻松地从内部引发异常以获得您期望的行为。也就是说 - 随时发布替代答案。
  • @JoãoPimentelFerreira Q 是一个图书馆。您可以将Q.delay(N) 替换为new Promise(r => setTimeout(r, N)),从此过上幸福的生活:)
【解决方案2】:

以下是我如何处理此问题的示例,其中包含一些辅助函数。请注意,“maxTimeout”是更复杂的部分,因为您必须在两个状态之间进行竞赛。

// Helper delay function to wait a specific amount of time.
function delay(time){
    return new Promise(function(resolve){
        setTimeout(resolve, time);
    });
}

// A function to just keep retrying forever.
function runFunctionWithRetries(func, initialTimeout, increment){
    return func().catch(function(err){
        return delay(initialTimeout).then(function(){
            return runFunctionWithRetries(
                    func, initialTimeout + increment, increment);
        });
    });
}

// Helper to retry a function, with incrementing and a max timeout.
function runFunctionWithRetriesAndMaxTimeout(
        func, initialTimeout, increment, maxTimeout){

    var overallTimeout = delay(maxTimeout).then(function(){
        // Reset the function so that it will succeed and no 
        // longer keep retrying.
        func = function(){ return Promise.resolve() };
        throw new Error('Function hit the maximum timeout');
    });

    // Keep trying to execute 'func' forever.
    var operation = runFunctionWithRetries(function(){
        return func();
    }, initialTimeout, increment);

    // Wait for either the retries to succeed, or the timeout to be hit.
    return Promise.race([operation, overallTimeout]);
}

然后要使用这些帮助程序,您需要执行以下操作:

// Your function that creates a promise for your task.
function doSomething(){
    return new Promise(...);
}

runFunctionWithRetriesAndMaxTimeout(function(){
    return doSomething();
}, 1000 /* start at 1s per try */, 500 /* inc by .5s */, 30000 /* max 30s */);

【讨论】:

    【解决方案3】:

    我认为你不能在 Promise 级别上做到这一点 - Promise 不是一个操作,而只是一个将在未来到达的值,所以你不能定义一个类型为 Promise -> Promise 的函数,它将实现它。

    您需要向下一层定义一个类型为 Operation -> Promise 的函数,其中 Operation 本身的类型为 () -> Promise。我假设该操作不采用任何参数 - 您可以预先部分应用它们。

    这是一种递归方法,每次运行都会使超时时间加倍:

    function RepeatUntilSuccess(operation, timeout) {
        var deferred = Q.defer();
        operation().then(function success(value) {
            deferred.resolve(value);
        }, function error(reason) {
            Q.delay(timeout
            .then(function() {
                return RepeatUntilSuccess(operation, timeout*2);
            }).done(function(value) {
                deferred.resolve(value);
            });
        });
        return deferred.promise;
    }
    

    演示:http://jsfiddle.net/0dmzt53p/

    【讨论】:

    • 为什么要延期?您可以使用承诺链。
    • 酷,谢谢你的展示!还在学习这些,我总是不够直截了当。 (还没有在小提琴之外尝试它们)
    • 好吧,我 wrote a Q&A about this specific issue 基于 Petka 的(链接在那里)。
    【解决方案4】:
    1. 为“所有进程超时”分配一个布尔变量。
    2. 在“所有进程超时”之后调用窗口的 setTimeout 以使该变量为“假”。
    3. 调用 promise 操作超时。
    4. 如果成功没问题。
    5. 如果失败:在 Promise 的错误处理程序中,如果布尔变量为 true,则再次调用 Promise 函数并增加超时时间。

    类似这样的:

    var goOn= true;
    
    setTimeout(function () {
        goOn= false;
    }, 30000); // 30 seconds -- all process timeout
    
    
    var timeout = 1000; // 1 second
    
    (function () {
        var helperFunction = function () {
    
            callAsyncFunc().then(function () {
                // success...
            }, function () {
                // fail
                if (goOn) {
                    timeout += 1000; // increase timeout 1 second
                    helperFunction();
                }
            }).timeout(timeout);
    
        }
    })();
    

    【讨论】:

    • 我想你可能会对.timeout 的作用感到困惑(它实际上做了你使用goOn 的事情,因为它只是内置在库中)。
    【解决方案5】:

    我用 Promises/A+ 做了以下事情(用 Q 应该没问题)

    function delayAsync(timeMs)
    {
        return new Promise(function(resolve){
            setTimeout(resolve, timeMs);
        });
    }
    
    //use an IIFE so we can have a private scope
    //to capture some state    
    (function(){
        var a;
        var interval = 1000;
        a = function(){
            return doSomethingAsync()
                .then(function(success){
                    if(success)
                    {
                        return true;
                    }
                    return delayAsync(interval)
                             .then(function(){
                                 interval *= 2;
                             })
                             .then(a());
                });
        };
        a();
    })();
    

    我相信您可以弄清楚如何在最大超时后保释。

    【讨论】:

    • 你的意思是打电话给a吗?
    • @BenjaminGruenbaum 是的。很抱歉遗漏了。
    【解决方案6】:

    我的建议是使用 bluebird-retry

    安装

    npm i bluebird-retry

     var Promise = require('bluebird');
     var retry = require('bluebird-retry');
     var count = 0;
    function myfunc() {
        console.log('myfunc called ' + (++count) + ' times '+new Date().toISOString());
        return Promise.reject(new Error(''));
    }
    retry(myfunc,{ max_tries: 5, interval: 5000, backoff: 2 })
        .then(function(result) {
            console.log(result);
        });
    

    上述程序以interval * backoff每次重试之间的退避间隔尝试承诺流5次。

    另外,如果您需要传递任何参数,请将其作为args 传递,它接受参数数组。将其包含在提及max_retriesintervalbackoff 的选项部分中。

    这里是官方文档https://www.npmjs.com/package/bluebird-retry

    【讨论】:

      猜你喜欢
      • 2016-02-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-06-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多