javascriptnode.jspromise

Promise Retry Design Patterns


  1. Pattern that keep on retrying until the promise resolves (with delay and maxRetries).
  2. Pattern that keeps on retrying until the condition meets on the result (with delay and maxRetries).
  3. A memory efficient dynamic Pattern with unlimited retries (delay provided).

Code for #1. Keeps on retrying until promise resolves (any improvements community for the language etc?)

Promise.retry = function(fn, times, delay) {
    return new Promise(function(resolve, reject){
        var error;
        var attempt = function() {
            if (times == 0) {
                reject(error);
            } else {
                fn().then(resolve)
                    .catch(function(e){
                        times--;
                        error = e;
                        setTimeout(function(){attempt()}, delay);
                    });
            }
        };
        attempt();
    });
};

Use

work.getStatus()
    .then(function(result){ //retry, some glitch in the system
        return Promise.retry(work.unpublish.bind(work, result), 10, 2000);
    })
    .then(function(){console.log('done')})
    .catch(console.error);

Code for #2 keep on retrying until a condition meets on the then result in a reusable way (condition is what will vary).

work.publish()
    .then(function(result){
        return new Promise(function(resolve, reject){
            var intervalId = setInterval(function(){
                work.requestStatus(result).then(function(result2){
                    switch(result2.status) {
                        case "progress": break; //do nothing
                        case "success": clearInterval(intervalId); resolve(result2); break;
                        case "failure": clearInterval(intervalId); reject(result2); break;
                    }
                }).catch(function(error){clearInterval(intervalId); reject(error)});
            }, 1000);
        });
    })
    .then(function(){console.log('done')})
    .catch(console.error);

Solution

  • Something a bit different ...

    Async retries can be achieved by building a .catch() chain, as opposed to the more usual .then() chain.

    This approach is :

    Otherwise, use a recursive solution.

    First, a utility function to be used as a .catch() callback.

    var t = 500;
    
    function rejectDelay(reason) {
        return new Promise(function(resolve, reject) {
            setTimeout(reject.bind(null, reason), t); 
        });
    }
    

    Now you can build .catch chains very concisely :

    1. Retry until the promise resolves, with delay

    var max = 5;
    var p = Promise.reject();
    
    for(var i=0; i<max; i++) {
        p = p.catch(attempt).catch(rejectDelay);
    }
    p = p.then(processResult).catch(errorHandler);
    

    DEMO: https://jsfiddle.net/duL0qjqe/

    2. Retry until result meets some condition, without delay

    var max = 5;
    var p = Promise.reject();
    
    for(var i=0; i<max; i++) {
        p = p.catch(attempt).then(test);
    }
    p = p.then(processResult).catch(errorHandler);
    

    DEMO: https://jsfiddle.net/duL0qjqe/1/

    3. Retry until result meets some condition, with delay

    Having got your mind round (1) and (2), a combined test+delay is equally trivial.

    var max = 5;
    var p = Promise.reject();
    
    for(var i=0; i<max; i++) {
        p = p.catch(attempt).then(test).catch(rejectDelay);
        // Don't be tempted to simplify this to `p.catch(attempt).then(test, rejectDelay)`. Test failures would not be caught.
    }
    p = p.then(processResult).catch(errorHandler);
    

    test() can be synchronous or asynchronous.

    It would also be trivial to add further tests. Simply sandwich a chain of thens between the two catches.

    p = p.catch(attempt).then(test1).then(test2).then(test3).catch(rejectDelay);
    

    DEMO: https://jsfiddle.net/duL0qjqe/3/


    All versions are designed for attempt to be a promise-returning async function. It could also conceivably return a value, in which case the chain would follow its success path to the next/terminal .then().