async
是一个强大的异步流程控制库,其语义类似于js
对数组的操作。它提供了一系列非常强大而便捷的方法,有助于我们在javascript
单线程模型背景下写出优雅的逻辑控制代码。
先从文件操作开始初步了解async
函数库的作用:
filter
过滤出磁盘中存在的文件 const fs = require('fs');
const async = require('async');
async.filter(['f1', 'f2', 'f3'], function(it, callback) {
fs.access(it, function(err) {
callback(null, !err);
});
}, function(err, results) {
console.log(results);
});
假设当前目录下存在以上三个文件,那么results
输出为:
['f1', 'f2', 'f3']
map
判断文件是否存在 async.map(['f1', 'f2', 'f3'], function(it, callback) {
fs.exists(function(exists) {
callback(null, exists);
});
}, function(err, results) {
console.log(results);//[true, true, true]
});
以上两个例子分别使用了
access
与exists
判断文件是否存在,关于两个API
的详细说明,请查看nodejs
官方文档
function asyncTask(delay, arg) {
return function(callback) {
setTimeout(function() {
console.log(arg + "done");
callback(null, arg);
}, delay);
};
}
async.parallel([
asyncTask(10, "task1"),
asyncTask(1, "task2"),
asyncTask(100, "task3"),
], function(err, ret) {
console.log(ret);
});
asyncTask
输出的顺序依次为:
[luncher@localhost async]$ node app.js
task2done
task1done
task3done
//ret
[ 'task1', 'task2', 'task3' ]
可见虽然任务是并行的,但是最终的结果依然按照任务数组的顺序排列。
function asyncTask(delay, arg) {
return function(callback) {
setTimeout(function() {
console.log(arg + "done");
callback(null, arg);
}, delay);
};
}
async.series([
asyncTask(10, "task1"),
asyncTask(1, "task2"),
asyncTask(100, "task3"),
], function(err, ret) {
console.log(ret);
});
输出:
[luncher@localhost async]$ node app.js
task1done
task2done
task3done
[ 'task1', 'task2', 'task3' ]
可以看到任务按照传入的顺序依次执行。
each
each
函数依次遍历数组执行回调函数:
function readFile(file, callback) {
fs.readFile(file, function(err, ret) {
console.log("readFile "+ file + " done");
callback(err);
});
}
async.each(['f1', 'f2', 'f3'], readFile, function(err) {
console.log("all done");
});
需要注意的是,each不能保证迭代函数完成的顺序,这取决于用户的具体任务
forEachOf
each
函数可以对数组进行遍历,如果想要遍历的是一个对象,那么需要用到forEachOf
.
function readFile(file, k, callback) {
fs.readFile(file, function(err, ret) {
console.log("readFile "+ k + " " + file + " done");
callback(err);
});
}
async.forEachOf({k1: "f1", k2: "f2", k3: "f3"}, readFile, function(err) {
console.log("all done");
});
迭代函数第一个参数是value
,第二个参数是key
.
each
和foEachOf
的顺序 eachSeries
与forEachOf
都是用来控制迭代函数的顺序执行:
function readFile(file, k, callback) {
fs.readFile(file, function(err, ret) {
console.log("readFile "+ k + " " + file + " done");
callback(err);
});
}
async.forEachOfSeries({k1: "f1", k2: "f2", k3: "f3"}, readFile, function(err) {
console.log("all done");
});
这样程序的输出始终是:
[luncher@localhost async]$ node app.js
readFile k1 f1 done
readFile k2 f2 done
readFile k3 f3 done
all done
apply
包装异步任务 前面几个异步任务都需要人为的额外写一个函数,如asyncTask
,async
提供一个apply
语法糖用于解决此类问题:
// 使用 apply
async.parallel([
async.apply(fs.writeFile, 'testfile1', 'test1'),
async.apply(fs.writeFile, 'testfile2', 'test2'),
]);
// 等同于
async.parallel([
function(callback){
fs.writeFile('testfile1', 'test1', callback);
},
function(callback){
fs.writeFile('testfile2', 'test2', callback);
}
]);
Limit
类函数控制并发数量 async
为每个接口都提供了一个Limit
参数,用户限制并发数量,我们利用filterLimit
做一个简单的测试:
var count = 0;
async.filterLimit([1, 10, 28, 90, 10], 2, function(it, callback) {
count++;
console.log(it + "开始");
console.log("并发数:", count);
setTimeout(function() {
count--;
console.log(it + "结束");
callback(null);
}, it);
}, function(results) {
console.log(results);
});
输出:
[luncher@localhost async]$ node app.js
1开始
并发数: 1
10开始
并发数: 2
1结束
28开始
并发数: 2
10结束
90开始
并发数: 2
28结束
10开始
并发数: 2
10结束
90结束
null
可以看到,最大并发数也就是2
,虽然待执行任务大于2
.
async
几乎提供了全类数组操作类型的接口,例如:sortBy
、reduce
、some
等,这里不再一一展开。
前面介绍了两个异步流程控制函数:series
和parallel
。下面介绍其他几个流程控制函数:
whilst
实现while
操作 whilst
用于实现,类似于while
的效果,直到满足条件,否则持续执行回调函数。
var c = 0;
async.whilst(function() {
console.log("judge");
return c < 3;
}, function(callback) {
c++;
console.log("try " + c);
setTimeout(function() {
callback(null, c);
}, 1000);
}, function(err, val) {
console.log('err', err);
console.log('value: ', val);
});
输出:
[luncher@localhost async]$ node app.js
judge
try 1
judge
try 2
judge
try 3
judge
err null
value: 3
waterFall
解决异步任务依赖问题 async.waterfall([
function(callback) {
callback(null, 'one', 'two');
},
function(arg1, arg2, callback) {
// arg1 now equals 'one' and arg2 now equals 'two'
callback(null, 'three');
},
function(arg1, callback) {
// arg1 now equals 'three'
callback(null, 'done');
}
], function (err, result) {
// result now equals 'done'
});