什么非阻塞,异步I/O就不多说了。这里简单概括一下js的异步编程模式,但是别指望下面的东西能让你学会什么。最好的学习方式是实践。
PubSub模式,也就是订阅分发。
Node的API构架师因为太喜欢PubSub,所以就直接有了一个实体叫EventEmitter对象,几乎所有的I/O对象都继承了EvenEmitter。事件->事件处理器,是最基本的js设计模式。
Promise
尽管PubSub模式已经非常多才多艺,但是并不是适用于所有任务的万能工具,尤其是解决类似一次性事件的问题。比如对一次性任务的两种结果做不同的处理。 在之前的jQuery版本里
$.get('/mydata',{
succeess:onSuccess,
failure:onFail,
always:onAlways
})
在新版本的jQuery里
var promise = $.get('/mydata');
promise.done(onSuccess);
promise.fail(onFailure);
promise.always(onAlways);
jQuery的promise和CommmonJs的Promises/A大部分是一样的。 promise 还可以进行合并,比如
gameReady = $.when(tutorialPromise,gameLoadedPromise)
gameReady.done(startGame)
Promise越来越流行,就有越来越多的js库会要求异步函数必须返回promise对象,代替回调函数。
Async.js 工作流控制
前面都是对对象的抽象设计来控制异步的布局。但是,假设需要执行一组IO操作(或者并行,或者串行),该怎么办呢? 这个问题在Nodejs里面的确是必须面对的事情。以至于有了一个专有名词流程控制。Async.js是npm里面,人气相当高的 一个流程控制库。类似的还有一些轻量级的比如eventproxy.js,和step.js,都是非常棒的流程控制库。 比如
async.filter async.foreach 会并行处理数组
async.filterseries async.foreachSeries 会顺序处理数组
reject/rejectSeries 和filter 相反
map/mapSeries 1:1映射
reduce/reduceRight 值的逐步变换
detect/detectSeries 找到筛选器匹配的值
sortBy 产生有序副本
some 测试至少有一个值符合给定标准
every 测试是否所有值均符合给定标准
Async.js的精髓就是能够以最低的代码重复度来执行常见的迭代工作。
还有例如
async.series 异步函数顺序执行
async.parallel 异步函数并行执行
async.queue 动态队列排队技术,如下
async = require 'async'
worker (data,cb){
console.log(data)
cb()
}
concurrency = 2
queue = async.queue(worker,concurrency)
queue.push(1)
queue.push(2)
queue.push(3)
只要并发度不小于1,结果都是
1
2
3
区别是并发度为1需要三轮遍历,并发度为2需要2两轮遍历才能遍历完,并发度大于3的话,一轮就够了。
如果你的应用需要工作流程控制,那就需要找一个好的轮子,并且掌握它,我推荐上面提到的那些流程控制库。
worker对象的多线程技术
事件是多线程技术的替代品,但是多核cpu盛行的当下,我们需要多线程编程技术。那是否意味着就要放弃基于事件的编程呢?
多核任务调度是非常麻烦的,为了保持程序的纯洁性,最好让一个cpu单独执行一个任务,只是偶尔同步一下。 js中的worker就是这么干的。
网页版中的worker是HTML5标准的一部分,下面是个例子
//mian script
var worker = new Worker('boknows.js')
worker.addEventLisener('message',function(e){
console.log(e.data)
})
worker.postMessage('football')
worker.postMessage('baseball')
//boknow.js
self.addEventListener('message',function(e){
self.postMessage('Bo Nonws'+e.data)
})
在nodejs中cluster就是nodejs版的worker
cluster = 'cluster'
if(cluster.isMaster){
coreCount = require('os').cpus().length;
for(i=0;i<coreCount;i++){
cluster.fork()
cluster.on('death',function(worker){
console.log('Worker '+worker.pid+'has died')
})
}
}else{
process.exit()
}
输出如下
worker 15530 has died
worker 15532 has died
worker 15529 has died
worker 15531 has died
每一行输出对应一个cpu,但是worker到底是不是分配在一个独立的cpu上还要看底层的操作系统。
异步脚本加载
很多时候,我们需要为把.js放在head还是放在body后面担忧。利用defer,async这些属性也能处理很多情况。
这个时候可编程的条件加载出现了。比如yepnope这种轻量的脚本加载库
yepnope({
load:'js.js',
callback:function(){
console.log('ready')
}
})
还有Require.js/AMD的智能加载 加载示例
require(['moment'],function(moment){
console.log(moment().format('dddd'))
})
AMD模块定义示例
define('myApplication',['jquery'],function(){
$('<body>').append('<p>hello,async world</p>')
})
这样可以解决模块加载依赖等问题。
异步模式大概如此,但远不止如此。