Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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
like image 935
Rockbob Avatar asked Feb 04 '16 08:02

Rockbob


2 Answers

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.

like image 58
jfriend00 Avatar answered Sep 20 '22 02:09

jfriend00


For retry http call when it come with error. but first you need to check this error be retry-able or not.

RETRIABLE_NETWORK_ERRORS = ['ECONNRESET', 'ENOTFOUND', 'ESOCKETTIMEDOUT', 'ETIMEDOUT', 'ECONNREFUSED', 'EHOSTUNREACH', 'EPIPE', 'EAI_AGAIN'];

If error comes under RETRIABLE_NETWORK_ERRORS than you need to come under retry logic otherwise mark as error.

For retry logic use exponential backoff algorithm. you follow https://developers.google.com/api-client-library/java/google-http-java-client/backoff

const _ = require('lodash');
const request = require('request');

var httpArray = getHttpReq(); //return array

function makeHttpRequest() {
    _.each(httpArray, function (httpRequest) {
        retryRequest(httpRequest);
    });
}

function retryRequest(httpRequest) {

    const MAX_RETRY = 2;
    var retryCnt = 0;
    Retry();

    function Retry() {

        request(httpRequest, function (error, response, body) {
            if (!error && response.statusCode == 200) {
                console.log(body)
            }
            else {
                if (retryCnt < MAX_RETRY) {
                    retryCnt += 1;
                    var currRetryIntervalMs = (1 << retryCnt) * 1000; //exponential back off logic
                    setTimeout(Retry, currRetryIntervalMs);
                }
                else {
                    console.log('http fail');
                }
            }
        });
    }
}
like image 43
eigenharsha Avatar answered Sep 18 '22 02:09

eigenharsha