[TOC]
高阶函数可以把函数作为参数,或是将函数作为返回值的函数,例如:
function foo(x) {
return function () {
return x
}
}
通过指定部分参数来产生一个新的定制函数的形式就是偏函数
var isType = function (type) {
return function (obj) {
return toString().call(obj) == '[object' + type + ']'
}
}
var isString = isType('String')
var isFunction = isType('Function')
Node带来的最大特性莫过于基于事件驱动的非阻塞I/O模型,这是它的灵魂所在。 非阻塞I/O可以使CPU与I/O并不相互依赖等待,让资源得到更好的利用。对于网络应用而言,并行带来的想象空间更大,延展而开的是分布式和云。并行使得各个单点之间能够更有效地组织起来,这也是Node在云计算厂商中广受青睐的原因。
-
难点1 异常处理
Node在处理一场上形成了一种约定,将异常作为回调函数的第一个实参传回,如果为空值,则表明异步调用没有异常跑出。在我们自行编写的异步方法上,也需要去遵循这样一些原则:
- 原则一:必须执行调用者传入的回调函数
- 原则二:正确传递回异常供调用者判断
-
难点2 函数嵌套过深
-
难点3 阻塞代码
-
难点4 多线程编程
-
难点5 异步转同步
- 事件发布/订阅模式
- Promise/Deferred模式
- 流程控制库
事件发布/订阅模式可以实现一个事件与多个回调函数的关联,这些回调函数又称为事件侦听器。通过emit()
发布事件后,消息会立即传递给当前事件的所有侦听器执行。侦听器可以很灵活地添加和删除,使得事件和具体处理逻辑之间可以很轻松的关联和解耦。
事件发布/订阅模式自身并无同步和异步调用的问题,但在Node中,emit()
调用多半是伴随事件循环而异步触发的,所以我们说事件发布/订阅广泛应用于异步编程。
事件发布/订阅模式常常用来解耦业务逻辑,事件发布者无须关注订阅的侦听器如何实现业务逻辑,甚至不用关注有多少个侦听器存在,数据通过消息的方式可以很灵活地传递。在一些典型的场景中,可以通过事件发布/订阅模式进行组件封装,将不变的部分封装在组件内部,将容易变化,需自定义的部分通过事件暴露给外部处理,这是一种典型的逻辑分离方式。在这种事件发布/订阅式组件中,事件的设计非常重要,因为它关乎外部调用组件时是否优雅,从某种角度来说事件的设计就是组件的接口设计。
从另一个角度来看,事件侦听器模式也是一种hook
,利用钩子导出内部数据或者状态给外部调用者。
在事件订阅/发布模式中,通常也有一个once()
方法,通过它添加的侦听器只能执行一次,在执行之后就会将它与事件的关联移除。这个特性常常可以帮助我们过滤一些重复性的事件响应。
多个异步场景中回调函数的执行并不能保证顺序,且回调函数之间相互没有任何交集,所以需要借助一个第三方函数和第三方变量来处理异步协作的结果。通常,我们把这个用于检测次数的变量叫做哨兵变量。
Promise是高级接口,事件是低级接口。低级接口可以构成更多更复杂的场景,高级接口一旦定义,不太容易变化,不再有低级接口的灵活性,但对于解决典型问题非常有效。
思考问题:Promise主要解决的是单个异步操作中存在的问题,当我们需要处理多个异步调用时又该如何处理呢?
var p1 = readFile('foo.txt', 'utf-8')
var p12 = readFile('bar.txt', 'utf-8')
var deferred = new Deferred()
deferred.all([p1, p2]).then (function(results) {
//TODO
}, function(err) {
//TODO
})
通过all()
方法抽象多个异步操作,只有所有异步操作成功,这个异步操作才算成功,一旦其中一个异步操作失败,整个异步操作就失败。
理想的编程体验应当是前一个的调用结果作为下一个调用的开始,是传说中的链式调用。
promise()
.then(obj.api1)
.then(obj.api2)
.then(obj.api3)
.then(obj.api4)
.then(function (value4) {
//TODO value4
}, function(error) {
//TODO error from step1 through setp4
})
.done()
要让promise支持链式执行,主要通过以下两个步骤
- 将所有回调都存在队列中
- promise完成时,逐个执行回调,一旦检测到返回了新的promise对象,停止执行,然后将当前的Deferred对象的promise引用改为新的promise对象,并将队列中余下的回调转交给它。
有一类需要手工调用才能持续执行后续调用,我们将此类方法叫做尾触发。常见关键词是next()。应用最多的地方是Connect的中间件。
- series 实现一组任务的串行执行,适合无依赖的异步
- parallel 并行执行异步操作
- waterfall 执行存在依赖的异步操作
- auto 自动分析依赖执行异步操作
接收任意数量的任务,所有的任务都将会串行一次执行。
事件发布/订阅模式相对算是一种较为原始的方式,Promise/Deferred
模式贡献了一个非常不错的异步任务模型的抽象。上面几种异步流程控制方案与Promise/Deferred
模式的思路不同,Promise/Deferred的重点在于封装异步的调用部分,流程控制库则显得没有模式,将处理重点放在回调函数的注入上。从自由度上来说,async
、setp
这类的流程控制库相对灵活得多。EventProxy
库则主要借鉴事件发布/订阅模式和流程控制库通过高阶函数生成回调函数的方式实现。
场景: 并发量过大时,下层服务器会吃不消,如果是对文件系统进行大量的并发调用,操作系统的文件描述符数量会被瞬间用光,抛出如下错误Error: EMFLIE, too many open files
。可以看出,异步I/O与同步I/O的明显差距:同步I/O因为每个I/O都彼此阻塞,在循环体中,总有一个接着一个调用,不会出现耗用文件描述符太多的情况,同时性能也是低下的;对于异步I/O,虽然并发容易实现,但是由于太容易实现依然需要控制。换言之,尽管要压榨底层系统的性能,但还是要给予一定的过载保护。
- 通过一个队列控制并发量
- 如果当前活跃(调用发起但未执行回调)的异步调用量小于限定值,从队列中取出执行
- 如果活跃调用达到限定值,调用暂时存放在队列中
- 每个异步调用结束时,从队列中取出新的异步调用执行
- parallelLimit 异步调用限制
- queue 动态增加并行任务