diff --git a/README.md b/README.md index 9763875e1..06ec85b9a 100644 --- a/README.md +++ b/README.md @@ -221,6 +221,7 @@ Some functions are also available in the following forms: * [`retry`](#retry) * [`iterator`](#iterator) * [`times`](#times), `timesSeries`, `timesLimit` +* [`race`](#race) ### Utils @@ -1655,6 +1656,46 @@ __Related__ --------------------------------------- + +### race(tasks, [callback]) + +Runs the `tasks` array of functions in parallel, without waiting until the +previous function has completed. Once any the `tasks` completed or pass an +error to its callback, the main `callback` is immediately called. It's +equivalent to `Promise.race()`. + +__Arguments__ + +* `tasks` - An array containing functions to run. Each function is passed + a `callback(err, result)` which it must call on completion with an error `err` + (which can be `null`) and an optional `result` value. +* `callback(err, result)` - A callback to run once any of the + functions have completed. This function gets an error or result from the + first function that completed. + +__Example__ + +```js +async.race([ + function(callback){ + setTimeout(function(){ + callback(null, 'one'); + }, 200); + }, + function(callback){ + setTimeout(function(){ + callback(null, 'two'); + }, 100); + } +], +// main callback +function(err, result){ + // the result will be equal to 'two' as it finishes earlier +}); +``` + +--------------------------------------- + ### memoize(fn, [hasher]) diff --git a/lib/index.js b/lib/index.js index a165909d4..2e27f25d9 100644 --- a/lib/index.js +++ b/lib/index.js @@ -42,6 +42,7 @@ import parallel from './parallel'; import parallelLimit from './parallelLimit'; import priorityQueue from './priorityQueue'; import queue from './queue'; +import race from './race'; import reduce from './reduce'; import reduceRight from './reduceRight'; import reject from './reject'; @@ -106,6 +107,7 @@ export default { parallelLimit: parallelLimit, priorityQueue: priorityQueue, queue: queue, + race: race, reduce: reduce, reduceRight: reduceRight, reject: reject, @@ -188,6 +190,7 @@ export { parallelLimit as parallelLimit, priorityQueue as priorityQueue, queue as queue, + race as race, reduce as reduce, reduceRight as reduceRight, reject as reject, diff --git a/lib/race.js b/lib/race.js new file mode 100644 index 000000000..4a5f8a59a --- /dev/null +++ b/lib/race.js @@ -0,0 +1,14 @@ +'use strict'; + +import isArray from 'lodash/isArray'; +import noop from 'lodash/noop'; +import once from 'lodash/once'; + +export default function race(tasks, cb) { + cb = once(cb || noop); + if (!isArray(tasks)) return cb(new TypeError('First argument to race must be an array of functions')); + if (!tasks.length) return cb(); + for (let i = 0; i < tasks.length; i++) { + tasks[i](cb); + } +} diff --git a/mocha_test/race.js b/mocha_test/race.js new file mode 100644 index 000000000..d56205dea --- /dev/null +++ b/mocha_test/race.js @@ -0,0 +1,70 @@ +var async = require('../lib'); +var assert = require('assert'); + +describe('race', function () { + it('should call each function in parallel and callback with first result', function raceTest10(done) { + var finished = 0; + var tasks = []; + function eachTest(i) { + var index = i; + return function (next) { + finished++; + next(null, index); + }; + } + for (var i = 0; i < 10; i++) { + tasks[i] = eachTest(i); + } + async.race(tasks, function (err, result) { + assert.ifError(err); + //0 finished first + assert.strictEqual(result, 0); + assert.strictEqual(finished, 1); + async.setImmediate(function () { + assert.strictEqual(finished, 10); + done(); + }); + }); + }); + it('should callback with the first error', function raceTest20(done) { + var tasks = []; + function eachTest(i) { + var index = i; + return function (next) { + setTimeout(function () { + next(new Error('ERR' + index)); + }, 50 - index * 2); + }; + } + for (var i = 0; i <= 5; i++) { + tasks[i] = eachTest(i); + } + async.race(tasks, function (err, result) { + assert.ok(err); + assert.ok(err instanceof Error); + assert.strictEqual(typeof result, 'undefined'); + assert.strictEqual(err.message, 'ERR5'); + done(); + }); + }); + it('should callback when task is empty', function raceTest30(done) { + async.race([], function (err, result) { + assert.ifError(err); + assert.strictEqual(typeof result, 'undefined'); + done(); + }); + }); + it('should callback in error the task arg is not an Array', function raceTest40() { + var errors = []; + async.race(null, function (err) { + errors.push(err); + }); + async.race({}, function (err) { + errors.push(err); + }); + assert.strictEqual(errors.length, 2); + assert.ok(errors[0] instanceof TypeError); + assert.ok(errors[1] instanceof TypeError); + }); +}); +