async.parallel With a Simple Timeout (node.js)

A while back I had a need for a general timeout option for async.parallel and was surprised I couldn’t find much information about how to do it. I was using async.parallel to make a lot of REST calls in parallel, but one of them was getting blocked by a firewall, resulting in an eventual timeout minutes later.

One option was to specify a shorter timeout for each individual REST call, including the one causing the problem. But I wanted a simpler solution: a general timeout for that specific async.parallel instance that made it clear all tasks should finish within a certain period of time. Otherwise return a timeout as an error, allowing the program to continue (and retry if necessary).

It was somewhat surprising there wasn’t an option built into async.parallel, as I could imagine other folks must have had the same problem at some point in time.

In any case, I wrote a pretty simple wrapper function that gets the job done (see example usage below):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
var async = require('async');

// async.parallel with optional timeout (options.timeoutMS)
function parallel(options, tasks, cb) {
	//  sanity checks
	options = options || {};

	// no timeout wrapper; passthrough to async.parallel
	if(typeof options.timeoutMS != 'number') return async.parallel(tasks, cb);

	var timeout = setTimeout(function(){
		// remove timeout, so we'll know we already erred out
		timeout = null;

		// error out
		cb('async.parallel timed out out after ' + options.timeoutMS + 'ms.', null);
	}, options.timeoutMS);

	async.parallel(tasks, function(err, result){
		// after all tasks are complete

		// noop if timeout was called and annulled
		if(!timeout) return;

		// cancel timeout (if timeout was set longer, and all parallel tasks finished sooner)
		clearTimeout(timeout);

		// passthrough the data to the cb
		cb(err, result);
	});
}


// example usage
parallel({timeoutMS: 10000}, [  // 10 second timeout
	function(){ ... },
	function(){ ... }
],
function(err, results) {
	if(err) {
		// timeouts can now be handled here
	}
});


// an example forcing a timeout to occur
parallel({
	timeoutMS: 1000   // 1 second timeout
},
[
	function(done){
		// task 1 completes in 100ms
		setTimeout(function(){
			done(null, 'foo');
		}, 100);
	},
	function(done){
		// task 2 completes in 2000ms, forcing a timeout error
		setTimeout(function(){
			done(null, 'bar');
		}, 2000);
	}
],
function(err, results) {
	// err = 'async.parallel timed out out after 1000ms.'
});