async.js 是我最喜欢的异步控制模块, 主要原因是用 async 写出来的代码相对如用 step.js 写出来的代码可读性更强. async.parallel, async.waterfall 这些API的命名也很形象.
近日想在浏览器端并发进行多个ajax请求, 于是便想把 async.js 放在浏览器端代码中, 可是一看源码才发现文件相对于我的需求而言–略大, 只好自己写个简化版的只支持 async.parallel.
1L 2L 提到的bug已经修复
https://gist.github.com/2016922
// This is an lite version of `async`, see https://github.com/caolan/async
//
// es5shim.js is required for old version browsers
//
// Author: Gui Lin
// Email: guileen[@gmail](/user/gmail).com
var async = {};
if (typeof module !== 'undefined' && module.exports) {
module.exports = async;
}
// example:
//
// async.parallel([
// function(_callback) {
// loadUser(uid, _callback);
// }
// , function(_callback) {
// loadBook(bookId, _callback);
// }
// ], function(err, results) {
// var user = results[0]
// , book = results[1]
// ;
// console.log(user);
// console.log(book);
// })
async.parallel = function (tasks, callback) {
var results = [], count = tasks.length;
tasks.forEach(function(task, index) {
task(function(err, data) {
results[index] = data;
if(err) {
callback(err);
callback = null;
}
if(--count === 0 && callback) {
callback(null, results);
}
});
});
}
这个实现是有问题的。
async.parallel
应当是当所有的异步任务都执行完毕时才调用最终的回调函数,而你的实现则是当数组中的最后一个任务执行完毕时就调用最终的回调函数,而此时前面的任务不一定执行完了。
考虑下面这个例子:
var a = function (callback) {
setTimeout(function() {
callback(null, 1);
}, 200);
};
var b = function (callback) {
setTimeout(function() {
callback(null, 2);
}, 100);
};
async.parallel([a, b], function(err, results) {
console.log(results[0] + results[1]);
});
函数 a 在 200 毫秒后执行完毕,而函数 b 在 100 毫秒后执行完毕,由于你的实现是当数组中的最后一个任务执行完毕时就回调了,此时 a 任务还没有完成,所以最终 log 下来的值为 undefined + 2
,即 NaN
正确的做法应当是维护一个计数器,用于统计当前已经完成的任务数目,当全部任务都完成时才调用最终的回调函数。
async 的做法是当其中一个任务失败的时候就直接callback(err)
,并将每个任务的回调函数设为空函数:function() {}
,以使剩余任务的回调无效化。
另外,其实 async 本来就很轻量(压缩后只有 1.7 KB),只是因为它在试图最大化的对逻辑进行抽象,以尽可能的重用现有的代码,所以导致其源码看上去有些复杂罢了。