在早期的异步开发中,如果有一些异步任务需要处理,难免会遇到回调地狱,为了解决这种问题,也出现过很多第三方库来避免,其中async.js就是比较有名的一个,里面有个waterfall方法,本文我们一起来模拟实现一个类似的

先来看下调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
waterfall([
function(cb) {
console.log(new Date);
setTimeout(function() {
cb(null, 123);
}, 2000);
},
function(arg, cb) {
console.log(new Date);
setTimeout(function() {
console.log(arg);
cb(null, 123, 456);
}, 2000);
},
function(arg1, arg2, cb) {
console.log(new Date);
console.log(arg1, arg2);
}
], function(ex) {
if (ex) {
throw ex;
}
});

下面我们一起来看下实现下waterfall这个方法 :

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
/**
* @param task 任务队列
* @param callback 最后的回调
**/
module.exports = function(task = [], callback = noop) {
// 类型判断
if (!(task instanceof Array)) {
return callback(new Error("task should be an array!"));
}
(function next(...args) {
// 第一个参数如果不为空就直接执行callback
if (args[0]) {
return callback(args[0]);
}
if (task.length) {
// 取得当前要执行的函数
let fn = task.shift();
// 第一个参数是error相关的,所以从第二个开始截取
fn.apply(null, [...args.slice(1), onlyOnce(next)]);
} else {
callback.apply(null, args);
}
})();
};
/**
* 包装一个函数确保它只被执行一次
**/
function onlyOnce(cb) {
let flag = false;
return function(...args) {
if (flag) {
return cb(new Error('cb already called'));
}
cb.apply(null, args);
flag = true;
};
}
function noop() {}

上面就是对waterfall方法的实现,在async.js还有很多其他很有用的方法,后面有机会继续模拟实现。