深入浅出node笔记(二)
函数式编程
在JS中,函数是一等公民,即函数可以作为函数参数,函数返回值,也可以赋值给变量
高阶函数
常规的函数接受的参数或者返回的值是基本数据类型或对象引用。高阶函数是指函数接受的参数或者返回的是函数。对于高阶函数而言,每次接受的函数不同,可以出现不同结果。在ES5中有许多的高阶函数:forEach、map、reduce、filter、every、some
偏函数
通过指定部分参数来产生一个定制函数,比如著名JS库underscore 中提供的after方法,underscore提供不添加内置对象而实现函数式编程的方法。
1 |
|
after函数的作用是:在times(>0)次调用才真正执行的函数。
异步编程
node最大的特点是事件驱动的非阻塞IO,非阻塞IO让CPU和IO之间不互相等待,提高资源利用。
前面提到过异步IO的原理是通过事件循环的方式,JS线程将任务分配给IO线程池的线程,
异步编程的难点在于:
1、异常处理
通常我们是try/catch来捕获异常,这只能捕获当次事件循环内的错误,对于下次事件循环(如process.nextTick)回调函数错误无能为力。
node在处理异常形成了一种约定:将异常作为回调函数的第一个参数,如果为空,表明异步调用没有异常。
2、阻塞代码
JS中没有sleep这样让线程沉睡的函数,可以通过setTimeout,setInterval来解决。
3、多线程编程
前端中,JS与UI渲染是同在主进程,可以使用web workers,独立于主线程的线程,分离JS计算和UI渲染,充分利用多核CPU。单个node进程是没有充分利用多核CPU的,node借鉴了这个模式,使用了child_process
异步编程解决方案
解决方案主要有:事件发布/订阅、Promise/Deferred,流程控制库
事件发布/订阅
node提供events模块简单实现发布/订阅,不存在事件冒泡,不存在preventDefault(阻止默认事件)、stopPropagation(阻止事件冒泡),具有on、once、removeListener、removeAllListeners和emit方法。发布订阅实现了一个事件与多个回调函数的关联。
继承events模块
1
2
3
4var events=require('events');
function Stream(){
events.EventEmitter.call(this);
util.inherits (Stream,events.EventEmitter);node在util中封装了继承的方法,直接调用就行,
事件队列解决雪崩问题
使用once方法只执行一次,执行后解除与事件的关联。雪崩是指在高访问、大并发量的情况下缓存失效,同时大量请求涌入数据库,导致影响整个网站响应速度。
1
2
3
4
5
6
7
8
9
10
11
12var proxy = new events.EventEmitter();
var status = "ready";
var select = function (callback){
proxy.once("selected", callback);
if (status === "ready"){
status = "pending";
db.select("sQL", function (results){
proxy.emit("selected", results);
status = "ready";
});
}
};利用once将请求回调压入事件队列中,保证每个回调只执行一次。对于相同的SQL查询,队列中的回调可以共享查询结果。
多异步的协作方案
在异步编程中会出现事件与回调函数是多对一的关系,业务可能依赖两个回调函数,回调函数间不饿能保证顺序,需要借助第三方函数和变量来协调。
Promise/Deferred
promise/A对单个异步操作这样抽象:promise只会在三种状态之一:pending、resolved、rejected,只能从pending变为resolve或reject,状态改变后不能更改。
在API的定义上,只要有then方法即可,then方法有以下要求:接受resolved、rejected的回调,只接受function对象,返回promise对象,以实现链式调用。
deferred主要维护内部状态的变化,promise用于外部,通过API then获取外部逻辑。与事件发布/订阅相比,promise是高级接口,事件是低级接口,低级接口的灵活性更高,高级接口一旦定义就不大容易变化,但对于解决经典问题十分有效。
promise通过all实现多个异步操作,只有当所有的异步操作成功,才会返回成功,否则就会失败。
书中使用node实现了链式调用,主要通过两个步骤:所有回调都存到队列中、promise完成时,逐个执行回调,检测到返回了新的promise对象时,改变deferred中promise的引用为新的promise对象,并将剩下的回调转给它。
流程控制库
事件发布/订阅和promise/deferred是经典或写进规范的解决方式,以下方式非模式化但更加灵活。
尾触发和next
尾触发是指,有的方法需要手动调用才能执行后续的调用,关键词是next。常用于connect的中间件,每个中间件传递请求对象、响应对象和尾触发函数,通过队列形成处理流。
next的原理是,取出队列中的中间件执行,同时将当前方法传入实现递归调用,达到持续触发的目的。
async
async基于promise,写法上更加简洁,不需要then,然后也不需要写匿名函数处理resolve
async提供series实现异步的串行执行,parallel实现异步的并行执行,waterfall实现异步的依赖处理,auto实现自动依赖处理,
step
step库比async更加轻量,只有一个接口Step,可以接受任意数量的任务,任务将串行执行。用到的this关键字其实是内部的next方法,将异步的结果传递给下一个任务
同样,Step通过this的parallel也可以实现多个异步并行执行,需要注意的是如果异步结果返回多个参数,Step只会取前2个参数,group实现按结果分组,
wind
文中还提到了wind库,其中使用了eval
异步并发控制
有时候,如果异步的并发量过大,下层服务器将吃不消,书中作者提供了一个bagpipe的解决方法,async提供了parallelLimit,用一个参数来控制异步并发的数量,缺点在于不能动态增加任务。queue可以实现动态添加并行任务。