深入浅出node笔记(二)

函数式编程

在JS中,函数是一等公民,即函数可以作为函数参数,函数返回值,也可以赋值给变量

高阶函数

常规的函数接受的参数或者返回的值是基本数据类型或对象引用。高阶函数是指函数接受的参数或者返回的是函数。对于高阶函数而言,每次接受的函数不同,可以出现不同结果。在ES5中有许多的高阶函数:forEach、map、reduce、filter、every、some

偏函数

通过指定部分参数来产生一个定制函数,比如著名JS库underscore 中提供的after方法,underscore提供不添加内置对象而实现函数式编程的方法。

1
2
3
4
5
6
7
8
_.after = function(times,func){
if (times <= 0) return func() ;
return function(){
if (--times< 1) { return func.apply(this,arguments);
}
};
};

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
    4
    var 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
    12
    var 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可以实现动态添加并行任务。

查看评论