Permalink
Browse files

added browser support

  • Loading branch information...
1 parent 4d09ada commit 353f4416e1c27333bf12e2260eeea5c28173e0c9 @caolan caolan committed Jul 7, 2010
Showing with 408 additions and 297 deletions.
  1. +39 −2 README.md
  2. +357 −279 lib/async.js
  3. +1 −1 package.json
  4. +11 −15 test/test-async.js
View
41 README.md
@@ -1,5 +1,6 @@
# Async
-_Higher-order functions and common patterns for asynchronous code in node.js_
+_Higher-order functions and common patterns for asynchronous code in node and
+the browser_
I've so far avoided using the existing async modules in favour of the standard
callbacks provided by node. When writing modules, I find sticking to the
@@ -22,10 +23,46 @@ to take a look at the new implementation of the old node Promise objects
([node-promise](http://github.com/kriszyp/node-promise)) or alternative
modules like [node-continuables](http://github.com/bentomas/node-continuables).
-__This module is also available as an npm package:__
+__This module enables you to do things like:__
+
+ async.map(['file1','file2','file3'], fs.stat, function(err, results){
+ // results is now an array of stats for each file
+ });
+
+ async.filter(['file1','file2','file3'], path.exists, function(results){
+ // results now equals an array of the existing files
+ });
+
+ async.parallel([
+ function(){ ... },
+ function(){ ... }
+ ], callback);
+
+ async.series([
+ function(){ ... },
+ function(){ ... }
+ ]);
+
+There are many more functions available so take a look at the API below for a
+full list. This module aims to be a comprehensive collection of async
+utilities, so if you feel anything is missing please create a GitHub issue for
+it.
+
+__Also available as an npm package:__
npm install async
+__Now works in the browser too!__
+So far its been tested in IE6, IE7, IE8, FF3.6 and Chrome 5. Usage:
+
+ <script type="text/javascript" src="async.js"></script>
+ <script type="text/javascript">
+
+ async.map(data, asyncProcess, function(err, results){
+ alert(results);
+ });
+
+ </script>
## API
View
636 lib/async.js
@@ -1,308 +1,386 @@
-var events = require('events');
+(function(exports){
+ //// cross-browser compatiblity functions ////
-exports.forEach = function(arr, iterator, callback){
- if(!arr.length) return callback();
- var completed = 0;
- arr.forEach(function(x){
- iterator(x, function(err){
- if(err){
- callback(err);
- callback = function(){};
- }
- else {
- completed++;
- if(completed == arr.length) callback();
- }
+ var _forEach = function(arr, iterator){
+ if(arr.forEach) return arr.forEach(iterator);
+ for(var i=0; i<arr.length; i++){
+ iterator(arr[i], i, arr);
+ }
+ };
+
+ var _map = function(arr, iterator){
+ if(arr.map) return arr.map(iterator);
+ var results = [];
+ _forEach(arr, function(x, i, a){
+ results.push(iterator(x, i, a));
+ })
+ return results;
+ };
+
+ var _reduce = function(arr, iterator, memo){
+ if(arr.reduce) return arr.reduce(iterator, memo);
+ _forEach(arr, function(x, i, a){
+ memo = iterator(memo, x, i, a);
});
- });
-};
-
-exports.forEachSeries = function(arr, iterator, callback){
- if(!arr.length) return callback();
- var completed = 0;
- var iterate = function(){
- iterator(arr[completed], function(err){
- if(err){
+ return memo;
+ };
+
+ var _keys = function(obj){
+ if(Object.keys) return Object.keys(obj);
+ var keys = [];
+ for(var k in obj){
+ if(obj.hasOwnProperty(k)) keys.push(k);
+ }
+ return keys;
+ };
+
+ var _indexOf = function(arr, item){
+ if(arr.indexOf) return arr.indexOf(item);
+ for(var i=0; i<arr.length; i++){
+ if(arr[i] === item) return i;
+ }
+ return -1;
+ };
+
+ //// nextTick implementation with browser-compatible fallback ////
+
+ var _nextTick;
+ if(typeof process === 'undefined' || !process.nextTick){
+ _nextTick = function(fn){
+ setTimeout(fn, 0);
+ };
+ }
+ else _nextTick = process.nextTick;
+
+
+ //// exported async module functions ////
+
+ exports.forEach = function(arr, iterator, callback){
+ if(!arr.length) return callback();
+ var completed = 0;
+ _forEach(arr, function(x){
+ iterator(x, function(err){
+ if(err){
+ callback(err);
+ callback = function(){};
+ }
+ else {
+ completed++;
+ if(completed == arr.length) callback();
+ }
+ });
+ });
+ };
+
+ exports.forEachSeries = function(arr, iterator, callback){
+ if(!arr.length) return callback();
+ var completed = 0;
+ var iterate = function(){
+ iterator(arr[completed], function(err){
+ if(err){
+ callback(err);
+ callback = function(){};
+ }
+ else {
+ completed++;
+ if(completed == arr.length) callback();
+ else iterate();
+ }
+ });
+ };
+ iterate();
+ };
+
+
+ var doParallel = function(fn){
+ return function(){
+ var args = Array.prototype.slice.call(arguments);
+ return fn.apply(null, [exports.forEach].concat(args));
+ };
+ };
+ var doSeries = function(fn){
+ return function(){
+ var args = Array.prototype.slice.call(arguments);
+ return fn.apply(null, [exports.forEachSeries].concat(args));
+ };
+ };
+
+
+ var _asyncMap = function(eachfn, arr, iterator, callback){
+ var results = [];
+ arr = _map(arr, function(x, i){
+ return {index: i, value: x};
+ });
+ eachfn(arr, function(x, callback){
+ iterator(x.value, function(err, v){
+ results[x.index] = v;
callback(err);
- callback = function(){};
- }
- else {
- completed++;
- if(completed == arr.length) callback();
- else iterate();
- }
+ });
+ }, function(err){
+ callback(err, results);
});
};
- iterate();
-};
+ exports.map = doParallel(_asyncMap);
+ exports.mapSeries = doSeries(_asyncMap);
-var doParallel = function(fn){
- return function(){
- var args = Array.prototype.slice.call(arguments);
- return fn.apply(null, [exports.forEach].concat(args));
- }
-};
-var doSeries = function(fn){
- return function(){
- var args = Array.prototype.slice.call(arguments);
- return fn.apply(null, [exports.forEachSeries].concat(args));
- }
-};
-
-
-var _map = function(eachfn, arr, iterator, callback){
- var results = [];
- arr = arr.map(function(x, i){
- return {index: i, value: x};
- });
- eachfn(arr, function(x, callback){
- iterator(x.value, function(err, v){
- results[x.index] = v;
- callback(err);
+ // reduce only has a series version, as doing reduce in parallel won't
+ // work in many situations.
+ exports.reduce = function(arr, memo, iterator, callback){
+ exports.forEachSeries(arr, function(x, callback){
+ iterator(memo, x, function(err, v){
+ memo = v;
+ callback(err);
+ });
+ }, function(err){
+ callback(err, memo);
});
- }, function(err){
- callback(err, results);
- });
-};
-exports.map = doParallel(_map);
-exports.mapSeries = doSeries(_map);
-
-
-// reduce only has a series version, as doing reduce in parallel won't
-// work in many situations.
-exports.reduce = function(arr, memo, iterator, callback){
- exports.forEachSeries(arr, function(x, callback){
- iterator(memo, x, function(err, v){
- memo = v;
- callback(err);
+ };
+ // inject alias
+ exports.inject = exports.reduce;
+ // foldl alias
+ exports.foldl = exports.reduce;
+
+ exports.reduceRight = function(arr, memo, iterator, callback){
+ var reversed = _map(arr, function(x){return x;}).reverse();
+ exports.reduce(reversed, memo, iterator, callback);
+ };
+ // foldr alias
+ exports.foldr = exports.reduceRight;
+
+ var _filter = function(eachfn, arr, iterator, callback){
+ var results = [];
+ arr = _map(arr, function(x, i){
+ return {index: i, value: x};
});
- }, function(err){
- callback(err, memo);
- });
-};
-// inject alias
-exports.inject = exports.reduce;
-// foldl alias
-exports.foldl = exports.reduce;
-
-exports.reduceRight = function(arr, memo, iterator, callback){
- var reversed = arr.map(function(x){return x;}).reverse();
- exports.reduce(reversed, memo, iterator, callback);
-};
-// foldr alias
-exports.foldr = exports.reduceRight;
-
-var _filter = function(eachfn, arr, iterator, callback){
- var results = [];
- arr = arr.map(function(x, i){
- return {index: i, value: x};
- });
- eachfn(arr, function(x, callback){
- iterator(x.value, function(v){
- if(v) results.push(x);
- callback();
+ eachfn(arr, function(x, callback){
+ iterator(x.value, function(v){
+ if(v) results.push(x);
+ callback();
+ });
+ }, function(err){
+ callback(_map(results.sort(function(a,b){
+ return a.index - b.index;
+ }), function(x){
+ return x.value;
+ }));
});
- }, function(err){
- callback(results.sort(function(a,b){
- return a.index - b.index;
- }).map(function(x){
- return x.value;
- }));
- });
-};
-exports.filter = doParallel(_filter);
-exports.filterSeries = doSeries(_filter);
-// select alias
-exports.select = exports.filter;
-exports.selectSeries = exports.filterSeries;
-
-var _reject = function(eachfn, arr, iterator, callback){
- var results = [];
- arr = arr.map(function(x, i){
- return {index: i, value: x};
- });
- eachfn(arr, function(x, callback){
- iterator(x.value, function(v){
- if(!v) results.push(x);
- callback();
+ };
+ exports.filter = doParallel(_filter);
+ exports.filterSeries = doSeries(_filter);
+ // select alias
+ exports.select = exports.filter;
+ exports.selectSeries = exports.filterSeries;
+
+ var _reject = function(eachfn, arr, iterator, callback){
+ var results = [];
+ arr = _map(arr, function(x, i){
+ return {index: i, value: x};
});
- }, function(err){
- callback(results.sort(function(a,b){
- return a.index - b.index;
- }).map(function(x){
- return x.value;
- }));
- });
-};
-exports.reject = doParallel(_reject);
-exports.rejectSeries = doSeries(_reject);
-
-var _detect = function(eachfn, arr, iterator, main_callback){
- eachfn(arr, function(x, callback){
- iterator(x, function(result){
- result ? main_callback(x): callback();
+ eachfn(arr, function(x, callback){
+ iterator(x.value, function(v){
+ if(!v) results.push(x);
+ callback();
+ });
+ }, function(err){
+ callback(_map(results.sort(function(a,b){
+ return a.index - b.index;
+ }), function(x){
+ return x.value;
+ }));
});
- }, function(err){
- main_callback();
- });
-};
-exports.detect = doParallel(_detect);
-exports.detectSeries = doSeries(_detect);
-
-exports.some = function(arr, iterator, main_callback){
- exports.forEach(arr, function(x, callback){
- iterator(x, function(v){
- if(v){
- main_callback(true);
- main_callback = function(){};
- }
- callback();
+ };
+ exports.reject = doParallel(_reject);
+ exports.rejectSeries = doSeries(_reject);
+
+ var _detect = function(eachfn, arr, iterator, main_callback){
+ eachfn(arr, function(x, callback){
+ iterator(x, function(result){
+ if(result) main_callback(x);
+ else callback();
+ });
+ }, function(err){
+ main_callback();
});
- }, function(err){
- main_callback(false);
- });
-};
-// any alias
-exports.any = exports.some;
-
-exports.every = function(arr, iterator, main_callback){
- exports.forEach(arr, function(x, callback){
- iterator(x, function(v){
- if(!v){
- main_callback(false);
- main_callback = function(){};
- }
- callback();
+ };
+ exports.detect = doParallel(_detect);
+ exports.detectSeries = doSeries(_detect);
+
+ exports.some = function(arr, iterator, main_callback){
+ exports.forEach(arr, function(x, callback){
+ iterator(x, function(v){
+ if(v){
+ main_callback(true);
+ main_callback = function(){};
+ }
+ callback();
+ });
+ }, function(err){
+ main_callback(false);
});
- }, function(err){
- main_callback(true);
- });
-};
-// all alias
-exports.all = exports.every;
-
-exports.sortBy = function(arr, iterator, callback){
- exports.map(arr, function(x, callback){
- iterator(x, function(err, criteria){
- if(err) callback(err);
- else callback(null, {value: x, criteria: criteria});
+ };
+ // any alias
+ exports.any = exports.some;
+
+ exports.every = function(arr, iterator, main_callback){
+ exports.forEach(arr, function(x, callback){
+ iterator(x, function(v){
+ if(!v){
+ main_callback(false);
+ main_callback = function(){};
+ }
+ callback();
+ });
+ }, function(err){
+ main_callback(true);
});
- }, function(err, results){
- if(err) return callback(err);
- else callback(null, results.sort(function(left, right){
- var a = left.criteria, b = right.criteria;
- return a < b ? -1 : a > b ? 1 : 0;
- }).map(function(x){return x.value;}));
- })
-};
-
-exports.auto = function(tasks, callback){
- callback = callback || function(){};
- var keys = Object.keys(tasks);
- if(!keys.length) return callback(null);
-
- var completed = [];
- var emitter = new events.EventEmitter();
- emitter.addListener('taskComplete', function(){
- if(completed.length == keys.length){
- callback(null);
- }
- });
+ };
+ // all alias
+ exports.all = exports.every;
- keys.forEach(function(k){
- var task = (tasks[k] instanceof Function)? [tasks[k]]: tasks[k];
- var taskCallback = function(err){
- if(err){
- callback(err);
- // stop subsequent errors hitting the callback multiple times
- callback = function(){};
- }
- else {
- completed.push(k);
- emitter.emit('taskComplete');
+ exports.sortBy = function(arr, iterator, callback){
+ exports.map(arr, function(x, callback){
+ iterator(x, function(err, criteria){
+ if(err) callback(err);
+ else callback(null, {value: x, criteria: criteria});
+ });
+ }, function(err, results){
+ if(err) return callback(err);
+ else callback(null, _map(results.sort(function(left, right){
+ var a = left.criteria, b = right.criteria;
+ return a < b ? -1 : a > b ? 1 : 0;
+ }), function(x){return x.value;}));
+ })
+ };
+
+ exports.auto = function(tasks, callback){
+ callback = callback || function(){};
+ var keys = _keys(tasks);
+ if(!keys.length) return callback(null);
+
+ var completed = [];
+
+ var listeners = [];
+ var addListener = function(fn){
+ listeners.unshift(fn);
+ };
+ var removeListener = function(fn){
+ for(var i=0; i<listeners.length; i++){
+ if(listeners[i] === fn){
+ listeners.splice(i, 1);
+ return;
+ }
}
};
- var requires = task.slice(0, Math.abs(task.length-1)) || [];
- var ready = function(){
- return requires.reduce(function(a,x){
- return (a && completed.indexOf(x) != -1);
- }, true);
+ var taskComplete = function(){
+ _forEach(listeners, function(fn){fn();});
};
- if(ready()) task[task.length-1](taskCallback);
- else {
- var listener = function(){
- if(ready()){
- emitter.removeListener('taskComplete', listener);
- task[task.length-1](taskCallback);
+
+ addListener(function(){
+ if(completed.length == keys.length){
+ callback(null);
+ }
+ });
+
+ _forEach(keys, function(k){
+ var task = (tasks[k] instanceof Function)? [tasks[k]]: tasks[k];
+ var taskCallback = function(err){
+ if(err){
+ callback(err);
+ // stop subsequent errors hitting callback multiple times
+ callback = function(){};
+ }
+ else {
+ completed.push(k);
+ taskComplete();
}
};
- emitter.addListener('taskComplete', listener);
- }
- });
-};
-
-exports.waterfall = function(tasks, callback){
- if(!tasks.length) return callback();
- callback = callback || function(){};
- var wrapIterator = function(iterator){
- return function(err){
- if(err){
- callback(err);
- callback = function(){};
- }
+ var requires = task.slice(0, Math.abs(task.length-1)) || [];
+ var ready = function(){
+ return _reduce(requires, function(a,x){
+ return (a && _indexOf(completed, x) != -1);
+ }, true);
+ };
+ if(ready()) task[task.length-1](taskCallback);
else {
- var args = Array.prototype.slice.call(arguments, 1);
- var next = iterator.next();
- if(next) args.push(wrapIterator(next));
- else args.push(callback);
- process.nextTick(function(){iterator.apply(null, args)});
+ var listener = function(){
+ if(ready()){
+ removeListener(listener);
+ task[task.length-1](taskCallback);
+ }
+ };
+ addListener(listener);
}
+ });
+ };
+
+ exports.waterfall = function(tasks, callback){
+ if(!tasks.length) return callback();
+ callback = callback || function(){};
+ var wrapIterator = function(iterator){
+ return function(err){
+ if(err){
+ callback(err);
+ callback = function(){};
+ }
+ else {
+ var args = Array.prototype.slice.call(arguments, 1);
+ var next = iterator.next();
+ if(next) args.push(wrapIterator(next));
+ else args.push(callback);
+ _nextTick(function(){iterator.apply(null, args);});
+ }
+ };
};
+ wrapIterator(exports.iterator(tasks))();
};
- wrapIterator(exports.iterator(tasks))();
-};
-
-exports.parallel = function(tasks, callback){
- callback = callback || function(){};
- exports.map(tasks, function(fn, callback){
- fn(function(err){
- var args = Array.prototype.slice.call(arguments,1);
- if(args.length <= 1) args = args[0];
- callback.call(null, err, args || null);
- });
- }, callback);
-};
-
-exports.series = function(tasks, callback){
- callback = callback || function(){};
- exports.mapSeries(tasks, function(fn, callback){
- fn(function(err){
- var args = Array.prototype.slice.call(arguments,1);
- if(args.length <= 1) args = args[0];
- callback.call(null, err, args || null);
- });
- }, callback);
-};
-
-exports.iterator = function(tasks){
- var makeCallback = function(index){
- var fn = function(){
- if(tasks.length) tasks[index].apply(null, arguments);
- return fn.next();
- }
- fn.next = function(){
- return (index < tasks.length-1)? makeCallback(index+1): undefined;
- }
- return fn;
+
+ exports.parallel = function(tasks, callback){
+ callback = callback || function(){};
+ exports.map(tasks, function(fn, callback){
+ if(fn){
+ fn(function(err){
+ var args = Array.prototype.slice.call(arguments,1);
+ if(args.length <= 1) args = args[0];
+ callback.call(null, err, args || null);
+ });
+ }
+ }, callback);
};
- return makeCallback(0);
-};
-exports.apply = function(fn){
- var args = Array.prototype.slice.call(arguments, 1);
- return function(){
- fn.apply(null, args.concat(Array.prototype.slice.call(arguments)));
+ exports.series = function(tasks, callback){
+ callback = callback || function(){};
+ exports.mapSeries(tasks, function(fn, callback){
+ if(fn){
+ fn(function(err){
+ var args = Array.prototype.slice.call(arguments,1);
+ if(args.length <= 1) args = args[0];
+ callback.call(null, err, args || null);
+ });
+ }
+ }, callback);
};
-}
+
+ exports.iterator = function(tasks){
+ var makeCallback = function(index){
+ var fn = function(){
+ if(tasks.length) tasks[index].apply(null, arguments);
+ return fn.next();
+ };
+ fn.next = function(){
+ return (index < tasks.length-1)? makeCallback(index+1): null;
+ };
+ return fn;
+ };
+ return makeCallback(0);
+ };
+
+ exports.apply = function(fn){
+ var args = Array.prototype.slice.call(arguments, 1);
+ return function(){
+ fn.apply(null, args.concat(Array.prototype.slice.call(arguments)));
+ };
+ };
+
+})((typeof exports == 'undefined') ? this['async']={}: exports);
View
2 package.json
@@ -2,7 +2,7 @@
, "description": "Higher-order functions and common patterns for asynchronous code"
, "main": "./index"
, "author": "Caolan McMahon"
-, "version": "0.1.1"
+, "version": "0.1.2"
, "repository" :
{ "type" : "git"
, "url" : "http://github.com/caolan/async.git"
View
26 test/test-async.js
@@ -50,7 +50,7 @@ exports['auto error'] = function(test){
}],
task3: function(callback){
callback('testerror2');
- },
+ }
},
function(err){
test.equals(err, 'testerror');
@@ -61,7 +61,7 @@ exports['auto error'] = function(test){
exports['auto no callback'] = function(test){
async.auto({
task1: function(callback){callback();},
- task2: ['task1', function(callback){callback(); test.done();}],
+ task2: ['task1', function(callback){callback(); test.done();}]
});
};
@@ -71,7 +71,7 @@ exports['waterfall'] = function(test){
async.waterfall([
function(callback){
call_order.push('fn1');
- process.nextTick(function(){callback(null, 'one', 'two');});
+ setTimeout(function(){callback(null, 'one', 'two');}, 0);
},
function(arg1, arg2, callback){
call_order.push('fn2');
@@ -105,7 +105,7 @@ exports['waterfall empty array'] = function(test){
exports['waterfall no callback'] = function(test){
async.waterfall([
function(callback){callback();},
- function(callback){callback(); test.done();},
+ function(callback){callback(); test.done();}
]);
};
@@ -308,7 +308,7 @@ exports['iterator'] = function(test){
test.equals(arg1, 'arg1');
test.equals(arg2, 'arg2');
call_order.push(3);
- },
+ }
]);
iterator();
test.same(call_order, [1]);
@@ -341,7 +341,7 @@ exports['iterator.next'] = function(test){
test.equals(arg1, 'arg1');
test.equals(arg2, 'arg2');
call_order.push(3);
- },
+ }
]);
var fn = iterator.next();
var iterator2 = fn('arg1');
@@ -612,9 +612,7 @@ exports['rejectSeries'] = function(test){
exports['some true'] = function(test){
async.some([3,1,2], function(x, callback){
- process.nextTick(function(){
- callback(x === 1);
- });
+ setTimeout(function(){callback(x === 1);}, 0);
}, function(result){
test.equals(result, true);
test.done();
@@ -623,9 +621,7 @@ exports['some true'] = function(test){
exports['some false'] = function(test){
async.some([3,1,2], function(x, callback){
- process.nextTick(function(){
- callback(x === 10);
- });
+ setTimeout(function(){callback(x === 10);}, 0);
}, function(result){
test.equals(result, false);
test.done();
@@ -655,7 +651,7 @@ exports['any alias'] = function(test){
exports['every true'] = function(test){
async.every([1,2,3], function(x, callback){
- process.nextTick(function(){callback(true);});
+ setTimeout(function(){callback(true);}, 0);
}, function(result){
test.equals(result, true);
test.done();
@@ -664,7 +660,7 @@ exports['every true'] = function(test){
exports['every false'] = function(test){
async.every([1,2,3], function(x, callback){
- process.nextTick(function(){callback(x % 2);});
+ setTimeout(function(){callback(x % 2);}, 0);
}, function(result){
test.equals(result, false);
test.done();
@@ -728,7 +724,7 @@ exports['detectSeries'] = function(test){
exports['sortBy'] = function(test){
async.sortBy([{a:1},{a:15},{a:6}], function(x, callback){
- process.nextTick(function(){callback(null, x.a);});
+ setTimeout(function(){callback(null, x.a);}, 0);
}, function(err, result){
test.same(result, [{a:1},{a:6},{a:15}]);
test.done();

0 comments on commit 353f441

Please sign in to comment.