关闭

《深入浅出Node.js》学习笔记——(四)异步编程

754人阅读 评论(0) 收藏 举报
分类:

Node能够迅速成功并流行的原因:

V8和异步I/O带来的性能提升

②前后端JavaScript编程风格一致

 

4.1 函数式编程

4.1.1高阶函数

可以将函数作为参数,或是作为返回值

4.1.2偏函数用法

指创建一个调用另外一个部分——参数或变量已经预置的函数——的函数的用法

 

4.2 异步编程的优势与难点

解决I/O性能的两个方案:①多线程 ②通过C/C++调用操作系统底层接口

4.2.1优势

Node的最大特性:基于事件驱动的非阻塞I/O

4.2.2难点

1.难点1:异常处理

异步I/O实现的两个阶段:提交请求、处理结果

异常并不一定发生在提交请求阶段,try/catch不会发生任何作用

异步方法的定义:

varasync = function (callback) {

process.nextTick(callback);

};

callback中的异常无能为力

varasync = function (callback) {

process.nextTick(callback);

};

解决方法:将异常作为回调函数的第一个实参传回,若为空值,则表明异步调用没有异常抛出

自行编写异步方法需要遵循的原则:

①必须执行调用者传入的回调函数

②正确传递回异常供调用者判断

varasync = function (callback) {

process.nextTick(function(){

varresults = something;

if(error) {

returncallback(error);

}

callback(null,results);

});

};

异步方法编写中容易犯的错误:对用户传递的回调函数进行异常捕获

try {

req.body= JSON.parse(buf, options.reviver);

callback();

} catch(err){

err.body= buf;

err.status= 400;

callback(err);

}

正确的捕获:

try {

req.body= JSON.parse(buf, options.reviver);

} catch(err){

err.body= buf;

err.status= 400;

returncallback(err);

}

callback();

2.难点2:函数嵌套过深

3.难点3:阻塞代码

4.难点4:多线程编程


node借鉴了这个模式,child_process是其基础APIcluster模块是更深层次的应用

5.难点5:异步转同步

通过良好的流程控制,将逻辑梳理成顺序式的形式

 

4.3 异步编程解决方案

异步编程的主要解决方案:①事件发布/订阅模式 Promise/Deferred模式 ③流程控制库

4.3.1事件发布/订阅模式

// 订阅

emitter.on("event1",function (message) {

console.log(message);

});

// 发布

emitter.emit('event1',"I am message!");

典型逻辑分离方式:通过事件发布/订阅模式进行组件封装,将不变的部分封装在组件内部,将容易变化、需自定义的部分通过事件暴露给外部处理。

组件中事件的设计即接口设计。

 

事件侦听器模式也是一种钩子机制,利用钩子导出内部数据或状态给外部的调用者。编程者不用关注组件是如何启动和执行的,只需关注在需要的事件点上即可。

 

Node基于健壮性对事件发布/订阅机制做的额外处理:

①对一事件添加超过10个侦听器将会得到一条警告。防止内存泄漏和过多占用CPU

调用emitter.setMaxListeners(0)可以将这个限制去掉。

②运行期间的出错时,eventemitter会检查是否对error事件添加过侦听器。添加了,则将错误交由侦听器处理,否则作为异常抛出。如果外部未捕获该异常,将引起线程退出。

1.继承events模块

varevents = require('events');

functionStream() {

events.EventEmitter.call(this);

}

util.inherits(Stream,events.EventEmitter);

2.利用事件队列解决雪崩问题

Once()方法添加的侦听器只能执行一次,之后与事件的关联移除。

例:数据库查询语句调用,访问量巨大

改进方案:①添加一个状态锁 ②使用once()方法

3.多异步之间的协作方案

①侦听器作为回调函数可以随意添加删除,随时添加业务逻辑

②也可以隔离业务逻辑,保持业务逻辑单元职责单一

一般事件与侦听器的关系为一对多,在异步编程中也会出现多对一的情况

需要借助哨兵变量

4.EventProxy的原理

来自于Backbone的事件模块

每次非all事件触发时触发一次all事件

5.EventProxy的异常处理

Fail()/done() 事件发布/订阅模式向promise模式的借鉴

4.3.2Promise/Deferred模式

先执行异步调用,延迟传递处理的方式

最早出现于dojo代码中,被广为所知来自于jQuery1.5版本

1.Promises/A

Promise操作的三种状态:未完成态、完成态、失败态

Promise的状态只会从未完成态向完成态或失败态转化,不能逆反。完成态和失败态不能相互转化

Promise的状态一旦转化,将不能被更改


一个Promise对象只要具备then()方法即可

对于then()方法的简单要求:

①接受完成态、错误态的回调方法

②可选地支持progress事件回调作为第三个方法

then()方法只接受function对象,其余对象将被忽略

then()方法继续返回promise对象,以实现链式调用


 


业务中不可变的部分封装在Deferred中,可变的部分交给Promise

Promise是高级接口,事件是低级接口

低级接口可构成复杂场景,高级接口不灵活,用于解决典型问题。

2.Promise中的多异步协作

简单原型实现:

Deferred.prototype.all= function (promises) {

varcount = promises.length;

var that= this;

varresults = [];

promises.forEach(function(promise, i) {

promise.then(function(data) {

count--;

results[i]= data;

if(count === 0) {

that.resolve(results);

}

},function (err) {

that.reject(err);

});

});

returnthis.promise;

};

多次文件读取场景:

varpromise1 = readFile("foo.txt", "utf-8");

varpromise2 = readFile("bar.txt", "utf-8");

vardeferred = new Deferred();

deferred.all([promise1,promise2]).then(function (results) {

// TODO

},function (err) {

// TODO

});

3.Promise的进阶知识

Promise的秘诀在于对队列的操作

Promise支持链式执行需要通过的两个步骤:

①将所有回调存到队列中

Promise完成时,逐个执行回调,一旦检测到返回了新的Promise对象,停止执行,然后将当前Deferred对象的promise引用改变为新的Promise对象,并将队列中余下的回调转交给它

4.3.3流程控制库

1.尾触发与Next

尾触发:需要手工调用才能持续执行后续调用

应用最多的地方是connect的中间件


Next()原理:取出队列中的中间件并执行,同时传入当前方法实现递归调用

并行逻辑处理需要搭配事件或者promise完成

connect中,尾触发适合处理网络请求的场景,将复杂的处理逻辑拆解为简洁、单一的处理单元,逐层次处理请求对象和响应对象

2.async

典型用法:

①异步的串行执行

Series()实现任务串行执行

回调函数由async通过高阶函数注入,每个callback()执行将结果保存起来,然后执行下一个调用,最终的回调函数执行时,队列里异步调用保存的结果以数组的方式传入。

异常处理规则:一旦出现异常,结束所有调用,并将异常传递给最终回调函数的第一个参数

②异步的并行执行

Parallel()实现任务并行执行

③异步调用的依赖处理

Waterfall()满足当前结果是后一个调用的输入的情况

④自动依赖处理

Auto()实现异步、同步混杂的复杂业务处理

3.Step

async更轻量

用到this关键字,是step内部的next()方法,将异步调用的结果传递给下一个任务作为参数,并调用执行

①并行任务执行

This.parallel()

注意:如果异步方法的结果传回多个参数,step只取前两个参数

stepparallel()原理:

每次执行将内部计数器加1,返回一个回调函数,在异步调用结束时执行,执行时计数器减1.计数器为0时,step执行下一个方法。

stepasync异常处理相同

②结果分组

Group()方法,类似parallel(),结果传递略有不同

Parallel()传递给下一个任务的结果是如下形式:

Function(err,result1,result2,…)

Group()传递的结果是:

Function(err,results)

返回的数据保存在数组中

4.wind

完全不同的思路,基于任务模型实现,提高一些场景下的异步编程体验

如冒泡排序中的动画效果

①异步任务定义

Eval(wind.compile("async",function(){}));定义了异步任务

Wind.async.sleep();内置了对setTimeout()的封装

$await()与任务模型

$await()是等待的占位符,其参数是一个任务对象

whenAll() 通过$await关键字将等待配置的所有任务完成后继续执行

③异步方法转换辅助函数

Wind.Async.Binding.fromCallback 用于转换无异常的调用

Wind.Async.Binding.fromStandard 用于转换带异常的调用

5.流程控制小结

①事件发布/订阅模式是较为原始的方式,Promise/Deferred模式贡献异步任务模型的抽象

,其重头在于封装异步的调用部分,流程控制库则显得没有模式,处理重点在回调函数的注入上。

async\step等流控库更灵活

EventProxy库主要借鉴事件发布/订阅模式和流程控制库通过高阶函数生成回调函数的方式实现

除上述以外,还有一类通过源代码编译的方案实现流程控制的简化,如streamline

4.4异步并发控制

若对文件系统进行大量并发调用,操作系统的文件描述符数量会瞬间用光

过载保护方案

4.4.1 bagpipe的解决方案

①通过一个队列来控制并发量

②若当前调用发起但未执行回调的异步调用量小于限定值,从队列中取出执行

③如果活跃调用达到限定值,调用暂时放在队列中

④每个异步调用结束时,从队列中取出新的异步调用执行

Push()方法和full事件

Next()方法主要判断活跃调用的数量,如果正常,调用内部方法run()来执行真正的调用

bagpipe允许异步调用并行进行,但严格限定上限

仅仅在调用push()时分开传递,并不对原有API有任何入侵

1)拒绝模式

在调用有实时方面需求时,快速失败,让调用方尽早返回

2)超时控制

控制每个调用的执行时间,设定阈值

4.4.2 async的解决方案

parallelLimit(),parallel()相比多了一个用于限制并发数量的参数

缺陷在于无法动态增加并行任务,queue()方法满足该需求

4.5总结

NODE基于V8,目前还不支持协程(coroutine)

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:3288次
    • 积分:81
    • 等级:
    • 排名:千里之外
    • 原创:5篇
    • 转载:0篇
    • 译文:0篇
    • 评论:0条
    文章存档