Charles Offenbacher
Charles Offenbacher

Reputation: 3132

How to end on first async parallel task completion in Node?

I have a list of tasks that I want to run in parallel using https://github.com/caolan/async.

I want the program to proceed (probably through a callback) after the first of these parallel tasks is complete, not all of them. So I don't think the naive

async.parallel([task1, task2], callback)

works for me.

Alternatively I could spawn two tasks and cancel the incomplete one, but I can't figure out how to do that using async either.

Thanks! -Charlie

Upvotes: 2

Views: 3409

Answers (2)

Daniel
Daniel

Reputation: 38771

Parallel Race

You can get async to initiate the final callback by returning an error that evaluates as true but isn't actually an error.

I've put together an example that uses -1 as an error code. In the final callback I check the error value and if it's not -1 then it's an actual error. If the error value is -1 then we'll have a valid value in results. At that point, we just need to remove extra elements from results of the other async functions that have not completed yet.

In the below example I've used the request module to pull html pages and the underscore module to filter the results in the final callback.

var request = require('request');
var _ = require('underscore');

exports.parallel = function(req, res) {
  async.parallel([
    /* Grab Google.jp */
    function(callback) {
      request("http://google.jp", function(err, response, body) {
        if(err) { console.log(err); callback(true); return; }
        callback(-1,"google.jp");
      });
    },
    /* Grab Google.com */
    function(callback) {
      request("http://google.com", function(err, response, body) {
        if(err) { console.log(err); callback(true); return; }
        callback(-1,"google.com");
      });
    }
    ],
    /* callback handler */
    function(err, results) {
      /* Actual error */
      if(err && err!=-1) {
        console.log(err);
        return;
      }
      /* First data */
      if(err===-1) {
        /*
         * async#parallel returns a list, one element per parallel function.
         * Functions that haven't finished yet are in the list as undefined.
         * use underscore to easily filter the one result.
         */
        var one = _.filter(results, function(x) {
          return (x===undefined ? false : true);
        })[0];
        console.log(results);
        console.log(one);
        res.send(one);
      }
    }
  );
};

Remaining Function Results

When you setup async#parallel to work like this you won't have access to the results of the other asynchronous functions. If you're only interested in the first one to respond then this isn't a problem. However, you will not be able to cancel the other requests. That's most likely not a problem, but it might be a consideration.

Upvotes: 6

RyanWilcox
RyanWilcox

Reputation: 13972

The async.parallel documentation says:

If any of the functions pass an error to its callback, the main callback is immediately called with the value of the error.

So you could return an error object from all of your parallel functors, and the first one to finish would jump you to the completion callback. Perhaps even your own special error class, so you can tell the difference between an actual error and a "hey I won" error.

Having said that, you would still have your parallel functions running, potentially waiting for callbacks to complete or whatever. Perhaps you could use async.parallelLimit to make sure you're not firing off too many tasks in parallel ?

Having said all that, it's possible you are better served by trying another method from the async library for this task - firing off parallel tasks then having these tasks race each other may not be the best idea.

Upvotes: 1

Related Questions