0

What is the best way to create parallel asynchronous HTTP requests and take the first result that comes back positive? I am familiar with the async library for JavaScript and would happy to use that but am not sure if it has exactly what I want.

Background - I have a Redis store that serves as state for a server. There is an API we can call to get some data that takes much longer than reaching the Redis store.

In most cases the data will already be in the Redis store, but in some cases it won't be there yet and we need to retrieve it from the API.

The simple thing to do would be to query Redis, and if the value is not in Redis then go to the API afterwards. However, we'll needlessly lose 20-50ms if the data is not yet in our Redis cache and we have to go to the API after failing to find the data with Redis. Since this particular API server is not under great load, it won't really hurt to go to the API simultaneously/in parallel, even if we don't absolutely need the returned value.

//pseudocode below

async.minimum([

function apiRequest(cb){
   request(opts,function(err,response,body){
         cb(err,body.result.hit);
    }
},
function redisRequest(cb){
   client.get("some_key", function(err, reply) {
          cb(err,reply.result.hit);
     });
}], 

  function minimumCompleted(err,result){

   // this mimimumCompleted final callback function will be only fired once, 
   // and would be fired by one of the above functions - 
   // whichever one *first* returned a defined value for result.hit


 });

is there a way to get what I am looking for with the async library or perhaps promises, or should I implement something myself?

Alexander Mills
  • 90,741
  • 139
  • 482
  • 817
  • It seems like promises might work well for this application because the first request to `resolve()` the promise will set the value and trigger any resolve handlers. The other requests, then will be ignored even if they also call resolve or reject on the promise since once the promise state is resolved/rejected, it can't be changed. – jfriend00 Sep 01 '15 at 04:42
  • yeah I agree, I think promises might work better for this use case, but not sure yet – Alexander Mills Sep 01 '15 at 04:49
  • also if you can think of a better title for this question please LMK thx – Alexander Mills Sep 01 '15 at 04:50
  • Looks like `async.parallel()` can be good fit as well. – Gaurav Gupta Sep 01 '15 at 04:55
  • no, async.parallel runs all functions in parallel and invokes the final callback only after all the functions have completed (fired their respective callbacks) – Alexander Mills Sep 01 '15 at 04:56
  • @AlexMills As OP wants to `create parallel asynchronous HTTP requests`, so isn't parallel() good for it. As far as `final callback invocation` is concerned, OP can, check the `respective callback` and when the `positive output is retrieved` then call 'callback({})'. This little adjustment needs to be made. If OP wants to retrieve `first positive outcome in final callback` then OP can store that in variable outside `parallel()` scope. Let me know if i am missing something. – Gaurav Gupta Sep 01 '15 at 05:02
  • Indeed it's a bit redundant to make 2 same* internal-requests for single external-request, both functions will be evaluated – Medet Tleukabiluly Sep 01 '15 at 05:06

3 Answers3

1

Use Promise.any([ap, bp]).

The following is a possible way to do it without promises. It is untested but should meet the requirements.

To meet requirement of returning the first success and not just the first completion, I keep a count of the number of completions expected so that if an error occurs it can be ignored it unless it is the last error.

function asyncMinimum(a, cb) {
    var triggered = false;
    var completions = a.length;
    function callback(err, data) {
      completions--;
      if (err && completions !== 0) return;
      if (triggered) return;
      triggered = true;
      return cb(err, data);
    }
    a.map(function (f) { return f(callback); });
}


asyncMinimum([

function apiRequest(cb){
   request(opts,function(err,response,body){
         cb(err,body.result.hit);
    }
},
function redisRequest(cb){
   client.get("some_key", function(err, reply) {
          cb(err,reply.result.hit);
     });
}], 

  function minimumCompleted(err,result){

   // this mimimumCompleted final callback function will be only fired once, 
   // and would be fired by one of the above functions - 
   // whichever one had a value for body.result.hit that was defined

 });
Dan D.
  • 73,243
  • 15
  • 104
  • 123
  • close, I think the asyncMinimum function has to account for whether the data value == null or not, but yeah it's pretty much the right idea. – Alexander Mills Sep 01 '15 at 04:54
  • actually I thought your code was pretty good, can you put it back in? Other users might find it useful. Keep the Promise.any at the top but mention the other code second, thanks – Alexander Mills Sep 01 '15 at 04:58
  • 1
    Done. I even improved the function to handle the mentioned case. – Dan D. Sep 01 '15 at 05:37
1

The async.js library (and even promises) keep track of the number of asynchronous operations pending by using a counter. You can see a simple implementation of the idea in an answer to this related question: Coordinating parallel execution in node.js

We can use the same concept to implement the minimum function you want. Only, instead of waiting for the counter to count all responses before triggering a final callback, we deliberately trigger the final callback on the first response and ignore all other responses:

// IMHO, "first" is a better name than "minimum":

function first (async_functions, callback) {
    var called_back = false;

    var cb = function () {
        if (!called_back) {
            called_back = true; // block all other responses

            callback.apply(null,arguments)
        }
    }

    for (var i=0;i<async_functions.length;i++) {
        async_functions[i](cb);
    }
}

Using it would be as simple as:

first([apiRequest,redisRequest],function(err,result){
    // ...
});
Community
  • 1
  • 1
slebetman
  • 109,858
  • 19
  • 140
  • 171
1

Here's an approach using promises. It takes a little extra custom code because of the non-standard result you're looking for. You aren't just looking for the first one to not return an error, but you're looking for the first one that has a specific type of result so that takes a custom result checker function. And, if none get a result, then we need to communicate that back to the caller by rejecting the promise too. Here's the code:

function firstHit() {
    return new Promise(function(resolve, reject) {
        var missCntr = 0, missQty = 2;

        function checkResult(err, val) {
            if (err || !val) {
                // see if all requests failed
                ++missCntr;
                if (missCntr === missQty) {
                    reject();
                }
            } else {
                resolve(val);
            }
        }
        request(opts,function(err, response, body){
            checkResult(err, body.result.hit);
        }

        client.get("some_key", function(err, reply) {
            checkResult(err, reply.result.hit);
        });
    });
}

firstHit().then(function(hit) {
    // one of them succeeded here
}, function() {
    // neither succeeded here
});

The first promise to call resolve() will trigger the .then() handler. If both fail to get a hit, then it will reject the promise.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • @AlexMills - yes it does. – jfriend00 Sep 01 '15 at 07:06
  • if you don't mind could you explain why a promise implementation might have any advantage over something promise-less? I have never really used promises, I am an async library fanboy – Alexander Mills Sep 01 '15 at 07:23
  • 1
    @AlexMills - there's been a lot written about that particular question so you'll probably get a lot more out of reading some of those articles. For me, I find the code easier to write and the error handling massively easier - particularly propagating async errors back several levels (which is really difficult with other mechanisms) and easier to chain operations and make compound/complicated multi-async operations. Some good articles [here](https://www.google.com/search?q=promises+vs.+async+library). – jfriend00 Sep 01 '15 at 20:13