Javascript的异步编程模式
发布于 11 年前 作者 ggaaooppeenngg 5662 次浏览 最后一次编辑是 8 年前

什么非阻塞,异步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>')
})

这样可以解决模块加载依赖等问题。


异步模式大概如此,但远不止如此。

2 回复

嗯 目前成熟的就差不多这些

总结到位,哈哈。 在nodejs里边用的多的是基于回调的async和基于事件的eventproxy 浏览器端使用requirejs解决模块依赖是趋势,业务逻辑里的异步和nodejs差不多

回到顶部