归档 2014 年 9 月

理解 Node.js 异步编程

事件队列

JavaScript 是单线程设计,任务的调度方式就是队列。即在运行过程中只有一个线程在运行,也就是说每次只能执行一个任务。当调用 setTimeout 这种异步函数的时候,会将控制权交给通信线程,其中的回调不会立即执行。而当调用赋值等同步语句的时候,会立即执行。JavaScript 虚拟机会轮询这个队列,执行合适的事件。

这样做有什么好处呢?

不用新开线程,减轻系统开销,再多的并发也只是加长了事件队列,也不会令执行混乱。

异步IO

一般同步执行的的 Web 程序中操作 IO 的函数总是执行时间最长的,当网络和存储技术不发生大的革新的情况下,异步IO 是一个提升响应速度的好办法。

同步IO 就是在执行 IO 操作的时候线程阻塞,不继续向下执行,而是一直等待这个 IO 操作的返回。 而当 JavaScript 虚拟机调用了异步IO函数以后,不会等待函数的返回,而是继续执行下一个事件。

那什么怎么知道异步函数执行完了呢?

IO 操作是另外的通信线程来实现的,通信线程操作结束后会将执行原始回调函数的引用加入到 JavaScript 事件队列中,JavaScript 虚拟机因为一直在轮询这个事件队列,会发现这个事件并完成后续的处理。对于 Node.js 应用而言,它是部分多线程,即非阻塞IO 为多线程,但是 IO 结果的处理还是单线程执行。

所以 Node.js 的执行过程能保证非常的迅速。

回调函数

回调函数也是一个函数,只不过它的调用方式跟普通的函数不一样,它是由事件驱动的。比如完成个某个IO 操作以后执行回调,那么这个回调这是由完成事件来触发的。

人脑最自然的思考方式是顺序执行,非常适合写同步代码。而异步理解起来会比较绕,你需要将下一个执行的事件封装成一个回调函数,然后当一个参数传递给异步函数。异步函数在完成它自己的操作以后再执行这个回调函数,而这个回调函数里面又可能有其他异步函数,里面也有回调函数,所以整个执行过程看起来会比较像一个金字塔。

比较合理的做法是将不同的回调封装成函数,在异步函数里面只做调用,这样代码的结构会清晰很多。但有时候还是会造成一些混乱,所以也有 Qasync 这些来填坑的库。

1