用async控制异步流程

写过JS的应该都知道,JS有个很蛋疼的地方,就是回调
一不小心,你的代码就会变成这样…..

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var GG = function() {
setTimeout(function() {
console.log(1);
setTimeout(function() {
console.log(2);
setTimeout(function() {
console.log(3);
setTimeout(function() {
console.log(4);
setTimeout(function() {
console.log(5);
}, 100);
}, 100);
}, 100);
}, 100);
}, 100);
};

GG();

看到这段代码我敢肯定每个人写JS的内心都有万匹草泥马呼啸奔腾而过…
要是再复杂点…中间再加点if判断什么的…这代码简直呵了个呵
嵌套加嵌套,摩擦摩擦
自己都不想看第二遍了吧

那么问题来了!

该如何写的不那么丑呢?其实也不算丑…主要我写代码比较好看,哦哈哈哈…
回归正题!该咋办呢?
幸好,我们碰到的问题前人已经碰到过了
于是诞生了一个牛逼的模块,也就是本文的主角——Async
这个模块就是用来解决异步流程控制问题的,接下来我们来看看上面这段代码如果用async写应该怎么写:
首先说一下函数功能,

async.series(tasks, [callback]) 是按顺序执行指定的函数,前一个结束了才会执行下一个

该写后的代码:
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
var async = require("async");

async.series([
function(callback) {
setTimeout(function() {
console.log(1);
callback(null, 1);
}, 100);
},
function(callback) {
setTimeout(function() {
console.log(2);
callback(null, 2);
}, 100);
},
function(callback) {
setTimeout(function() {
console.log(3);
callback(null, 3);
}, 100);
},
function(callback) {
setTimeout(function() {
console.log(4);
callback(null, 4);
}, 100);
},
function(callback) {
setTimeout(function() {
console.log(5);
callback(null, 5);
}, 100);
}
]);

什么?你说怎么这么长?+_+
可能我举得栗子不好…不过这不影响…你看,从上往下执行,这流程思路多清晰啊!多自然啊!
再也没有层层嵌套!

async.waterfall(tasks, [callback])
waterfall和series函数有很多相似之处,都是按顺序依次执行一组函数,不同之处是waterfall每个函数产生的值,都将传给下一个函数,而series则没有这个功能

为了更好的展示这个函数,我写了个爬虫:
具体如下:
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
var async = require("async");
var request = require('superagent');
var cheerio = require('cheerio');

var url = 'http://go.kieran.top';

async.waterfall([
function(callback) {
request
.get(url + '/archives')
.end(function(err, res) {
var $ = cheerio.load(res.text);
var urls = [];
$('#services .title a').each(function(id, elem) {
urls.push(url + $(elem).attr('href'));
});
callback(null, urls);
});
},
function(urls, callback) {
async.mapLimit(urls, 5, function(urlId, callback) { //并发数是5
var d = new Date();
console.log(d.getSeconds() + 's ' + d.getMilliseconds() + 'ms ' + '正在抓取:' + urlId);
request
.get(urlId)
.end(function(err, res) {
var $ = cheerio.load(res.text);
callback(null, $('#intro .col-md-8 h1').text());
});
}, function(err, res) {
console.log('文章标题:');
console.log(res);
callback();
});
}], function(err, res) {
console.log(res);
}
);

简单说就是第一个函数是从archives页获取所以文章的url,然后push到urls数组里,完成后把urls数组传递给第二个函数,第二个函数并发获取数组里链接的文章的title,并发数我随便写了个5,因为有些网站会因为你的并发数太多而把你的ip禁掉,虽然我的网站应该不会这样。

运行结果

> node test.js
19s 342ms 正在抓取:http://go.kieran.top/post/28/
19s 349ms 正在抓取:http://go.kieran.top/post/27/
19s 350ms 正在抓取:http://go.kieran.top/post/26/
19s 353ms 正在抓取:http://go.kieran.top/post/25/
19s 361ms 正在抓取:http://go.kieran.top/post/24/
19s 619ms 正在抓取:http://go.kieran.top/post/23/
19s 642ms 正在抓取:http://go.kieran.top/post/22/
19s 686ms 正在抓取:http://go.kieran.top/post/21/
19s 724ms 正在抓取:http://go.kieran.top/post/20/
19s 750ms 正在抓取:http://go.kieran.top/post/19/
19s 924ms 正在抓取:http://go.kieran.top/post/18/
19s 958ms 正在抓取:http://go.kieran.top/post/17/
20s 6ms 正在抓取:http://go.kieran.top/post/16/
20s 19ms 正在抓取:http://go.kieran.top/post/15/
20s 61ms 正在抓取:http://go.kieran.top/post/14/
20s 187ms 正在抓取:http://go.kieran.top/post/13/
20s 260ms 正在抓取:http://go.kieran.top/post/12/
20s 286ms 正在抓取:http://go.kieran.top/post/11/
20s 318ms 正在抓取:http://go.kieran.top/post/10/
20s 343ms 正在抓取:http://go.kieran.top/post/9/
20s 415ms 正在抓取:http://go.kieran.top/post/8/
20s 537ms 正在抓取:http://go.kieran.top/post/7/
20s 564ms 正在抓取:http://go.kieran.top/post/6/
20s 586ms 正在抓取:http://go.kieran.top/post/5/
20s 621ms 正在抓取:http://go.kieran.top/post/4/
20s 681ms 正在抓取:http://go.kieran.top/post/3/
20s 823ms 正在抓取:http://go.kieran.top/post/2/
20s 847ms 正在抓取:http://go.kieran.top/post/1/
文章标题:
[ ‘树莓派教程——HC-SR04 超声波测距模块’,
‘树莓派教程——BMP180 温度气压传感器’,
‘树莓派教程——人体红外感应模块HC-SR501加有源蜂鸣器实现简易报警装置’,
‘树莓派教程——DHT22温湿度传感器AM2302’,
‘树莓派教程——LED灯’,
‘树莓派初体验——配置篇’,
‘前端黑科技——纯clip-path制作属于你的个人动画’,
‘结果’,
‘非对称密码学实验:Python实现RSA的加解密’,
‘慕课网视频下载小程序’,
‘判断 IE 全版本浏览器的几种方法’,
‘2015 阿里面试小结’,
‘HTML5 + JavaScript 读取本地文件’,
‘aircrack-ng 命令简单记录’,
‘TKL-Hexo Theme’,
‘iframe && 网页底部 动态设置高度’,
‘HTML的一些小动“画”?’,
‘Node.js初体验——从一个简单的爬虫开始’,
‘浅谈CSRF防御-基础篇’,
‘记一次有趣的CSRF’,
‘聊聊 Google Project Zero’,
‘远程线程注入(0X02)注入第一个DLL’,
‘远程线程注入(0X01)第一个动态链接库’,
‘JavaScript操作注册表’,
‘信息隐藏——LSB算法’,
‘IntelliJ IDEA 14.0正式版激活算法’,
‘MBR,GPT的小科普’,
‘Hexo 简单优化’ ]

还有一些我自认为重要的函数我简单写下API吧,具体的还是去看官方Doc吧 https://github.com/caolan/async

parallel(tasks, [callback])
parallel函数是并行执行多个函数,每个函数都是立即执行,不需要等待其它函数先执行。 传给最终callback的数组中的数据按照tasks中声明的顺序,而不是执行完成的顺序
queue(worker, concurrency);
queue相当于一个加强版的parallel,主要是限制了worker数量,不再一次性全部执行。当worker数量不够用时,新加入的任务将会排队等候,直到有新的worker可用。
它有多个点可供回调,如无等候任务时(empty)、全部执行完时(drain)等。

总结

串行控制:
series、waterfall、compose;
并行控制:
parallel、parallelLimit、queue;
循环控制:
whilst、doWhilst、until、doUntil、forever;
其他控制:
apply‘applyEach、iterator、auto;

学习了async模块后,对一般的函数嵌套问题都能够引刃而解,最后感谢读到这里的人,不嫌弃博主的烂代码…TAT