[关闭]
@frank-shaw 2019-07-03T14:18:25.000000Z 字数 2458 阅读 2543

《深入浅出Node.JS》中的异步机制相关部分

node.js


Node.js简介

Node打破了过去JavaScript只能在浏览器运行的局面。它使得前后端编程环境实现了统一,可大大降低前后端转换所需要的上下文交换代价。

Node特点

异步I/O

在Node中,绝大多数的操作都以异步的方式进行调用。Ryan Dahl排除万难,在底层构建了很多异步I/O的API,这样我们可以在Node中可以从语言层面很自然地进行并行I/O操作。

事件与回调函数

Node将前端浏览器中应用广泛且成熟的事件引入后端,配合异步I/O,将事件点暴露给业务逻辑。同时,回调函数也是最好的接受异步调用返回数据的方式。但是这可能会让很多程序员感到不习惯。

在转变为异步编程思维之后,通过对业务的划分和对事件的提炼,在流程控制方面处理业务的复杂度与同步方式实际上是一致的。

单线程

Node保持了JavaScript在浏览器中单线程的特点。

Node的异步I/O

在众多高级编程语言或运行平台中,将异步作为主要编程方式和设计理念的,Node是首个。便随着异步I/O的还有事件驱动和单线程,他们构成Node的基调。

为什么要异步I/O

可以从用户体验和资源分配这两点来分析。用户体验方面,前端通过异步可以消除掉UI阻塞的现象。资源分配方面,Node利用单线程,远离多线程死锁、状态同步等问题;同时利用异步I/O,让单线程远离阻塞,以更好使用CPU。

如何实现异步I/O

完成整个异步I/O环节,我们需要用到的是事件循环、观察者和请求对象等。核心就是通过主循环加事件触发的方式来运行程序。

事件循环(event loop)

在进程启动的时候,Node就会创建一个循环,每执行一次循环体的过程称为Tick。下面是示意图。事件循环是一个典型的生产者/消费者模型。异步I/O、网络请求都是事件的生产者,这些时间被传递到对应的观察者那里,事件循环则从观察者那里取出事件并处理。
111.jpg-24.2kB

观察者

在每个tick过程中,如何判断是否有事件需要处理呢?这里必须引入的概念就是观察者。每个事件循环中有一个或者多个观察者,而判断是否有事件需要处理的过程就是向这些观察者询问是否有要处理的事件。

请求对象

对于Node中的异步I/O调用而言,回调函数却不是由开发者来调用的。那么从我们发出调用后,到回调函数被执行,中间发生了什么呢?从JavaScript发起调用到内核执行完I/O操作的过渡过程中,存在一种中间产物,叫做请求对象。所有的状态都被保存在这个对象中,包括送入线程池等待执行以及I/O操作完毕后的回调处理。

I/O线程池

这是个重要的概念。你也可以将其与Java中的线程池的作用关联起来。
屏幕快照 2016-09-03 14.05.30.png-86.4kB

异步编程

优势

Node带来的最大特性就是基于事件驱动的非阻塞I/O模型。非阻塞I/O可以使得CPU与I/O并不相互依赖等待,让资源得到更好的利用。对于网络应用而言,并行可以为分布式和云带来更多的想象空间。

难点

1.函数嵌套过深。这也是Node经常被诟病的地方。
2.阻塞代码。JavaScript中竟然没有类似sleep()这样的线程沉睡功能。实际上你可以通过setTimeOut(function,time);这个函数来实现延迟调用的功能。
3.多线程编程。我们谈论JavaScript的时候,通常谈的是单一线程上执行的代码,这样无法很好利用多核CPU的优势。
4.异步难转同步。

异步编程解决方案

终于要到我的promise出场了!!!~

现如今,异步编程的主要解决方案有如下三种:

事件发布/订阅模式

事件监听器模式是一种广泛用于异步编程的模式,是回调函数的事件化。Node自身提供的events模块是事件发布/订阅模式一个简单实现,它具有addListener/on()、once() removeListener() removeAllListeners() 和emit()等基本的事件监听模式的方法实现。简单实例如下:

//订阅
emitter.on("event1",function(message){
    console.log(message);
});
//发布
emitter.emit('enent1',"I am a message!");

如何实现继承EventEmitter的类?简单的代码就可以实现,下图表示的是Node中的Stream对象继承EventEmitter的例子:

var events = require("events");

function Stream(){
    events.EventEmitter.call(this);
};
util.inherits(stream, events.EventEmitter);

事件发布/订阅模式自身并没有同步与异步调用的问题,但是在Node中,emit()调用多半是伴随事件循环而异步触发的。
事件发布/订阅模式常常用来解耦业务逻辑,事件发布者无需关注订阅的侦听器如何实现业务逻辑,甚至不用关注有多少个侦听器存在。典型应用场景中,通过事件发布/订阅模式进行组件封装,将不变的部分封装在组件内部,将容易变化、需要自定义的部分通过事件暴露给外部处理,这是一种典型的逻辑分离方式。

promise/Deferred模式

首先,看看之前所写的关于promise的基本认识:https://www.zybuluo.com/frank-shaw/note/468487

异步的广度使用使得回调、嵌套出现,但是一旦出现深度的嵌套,就会让编程的体验变得不愉快,而promise/Deferred模式在一定程度上缓解了这个问题。CommonJS草案现如今已经抽象出了Promises/A,Promises/B,Promises/D这样典型的异步Promises/Deferred模型,这使得异步操作可以以一种优雅的方式出现。

更详细的关于promise的描述,可以查看https://www.zybuluo.com/frank-shaw/note/489730

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注