Nodejs - Re-Calling function on error callback - Is there a non blocking way?

Rockbob picture Rockbob · Feb 4, 2016 · Viewed 7.1k times · Source

I got a function which makes a request to an API. Sometimes the API got some hiccups and isnt available for a second or two every now and then, resulting in an error on which I'd like to call the function again. Since there are another 70~80 lines of code following this callback, I wouldnt like to split the flow with an if(error) <do the same stuff> else <as here>

After trying for quite some time I ended up using a do-while(error) loop, which works but blocks. Is there an async way of doing this?

My code (simplified for generalization):

//This is the request part

function bar(j, callback){      
   j++;

   //simulated error
   if(j<=10){   
       return( callback('dont', j) );
   }
   //simulated success
   else{
       return( callback(null, j) );
   }

}

//This is the main function - part of a bigger piece in my code

function foo(){
   var i = 0;
   var err = 'yes'; //else we'd get an 'err is not defined'

   do{
     bar(i, function(error, j){
        i = j
        err = error;

        if(error){
          console.log(i); 
        }
        else{       
          return( console.log('done it!') );    
          // There's more here in my code
        }
     });
   } while (err);

   console.log('I blocked');
}

foo();

Edit:

For those interested, this is the output:

1

2

3

4

5

6

7

8

9

10

done it!

I blocked

Answer

jfriend00 picture jfriend00 · Feb 4, 2016

What I would suggest is that you make a function for your operation. If it fails, you set a short timer and retry after that timer fires. This will give you an asynchronous behavior between retries and other code in the sever can run.

function requestRetry(url, data, retryTimes, retryDelay, callback) {
    var cntr = 0;

    function run() {
        // try your async operation
        request(..., function(err, data) {
            ++cntr;
            if (err) {
                if (cntr >= retryTimes) {
                    // if it fails too many times, just send the error out
                    callback(err);
                } else {
                    // try again after a delay
                    setTimeout(run, retryDelay);
                }
            } else {
                // success, send the data out
                callback(null, data);
            }
        });
    }
    // start our first request
    run();
}


requestRetry(someUrl, someData, 10, 500, function(err, data) {
    if (err) {
        // still failed after 10 retries
    } else {
        // got successful result here
    }
});

This is a fairly simple retry scheme, it just retries on a fixed interval for a fixed number of times. More complicated schemes implement a back-off algorithm where they start with fairly quick retries, but then back-off to a longer period of time between retries after the first few failures to gives the server a better chance of recovering. If there happening to be lots and lots of clients all doing rapid retries, then you as soon as your server has a hiccup, you can get an avalanche failure as all the clients suddenly start rapidly retrying which just puts your serve in even more trouble trying to handle all those requests. The back-off algorithm is designed to allow a server a better chance of preventing an avalanche failure and make it easier for it to recover.

The back-off scheme is also more appropriate if you're waiting for the service to come back online after it's been down a little while.