理解Nodejs的Event Loop

Node的“event loop”主要是用来处理高输出量的。这很神奇,这也是为何node能够在单线程的状况下同时处理不少的后台操做。本文就会集中讲述event loop是怎么运行的,这样你能够可使用这个神奇的东西完成你本身的工做。javascript

 

事件驱动的编程(event-driven programming)java

要理解event loop首先须要了解的就是event driven programming(事件驱动的编程)。这个在1960年代就已经被人们所熟知。现在,event-driven proggramming被普遍的应用在UI处理中。javascript主要用在处理DOM中。node

定义很是简单:event-driven programming就是程序的控制流程是由事件或者状态的改变决定的。主要的实现机制就是用一个中心控制台监听事件,并在事件发生的时候调用这个事件对应的回调函数(状态的改变也是同样)。很熟悉吧?这就是node的event loop的处理机制。编程

浏览器中的javascript开发中经常会遇到.on*()的方法,好比element.onclick(),用于链接用户操做和DOM。这个模式在一个单一元素能够发出多种可能的事件的时候工做的很是好。Node在EventEmitter中使用了这个模式,这个模式主要用在了server,socket和‘http’等模块中。这在一个实例须要发出多种类型的事件和状态的时候很是有用。浏览器

另外一个广泛使用的模式是成功和失败。主流的实现方法有两种。第一个是发生错误的回调(原文:“error back”),就是在发生错误的时候把error做为第一个参数调用回调函数。另外一种方法是在ES6中定义的,使用Promises。闭包

‘fs’模块中大量使用了‘error back’。技术上来讲,某些调用会发出其余的事件。好比,fs.readFile()。可是只在成功或者失败的时候才提醒用户。API这么设计主要是出于系统策略,不是技术的限制。app

两一个很大的误解是事件发生机制自己是异步的。可是,这是不对的。下面的代码会代表这一点:异步

var EventEmitter = require('events').EventEmitter;
var util = require('util');

function MyEmitter() {
    EventEmitter.call(this);
}
util.inherits(MyEmitter, EventEmitter);

MyEmitter.prototype.doStuff = function() {
    console.log('before');
    this.emit('fire');
    console.log('after');
}

var me = new MyEmitter();
me.on('fire', function(){
    console.log('emit fired');
});

me.doStuff();

// Output:
// before
// emit fired
// after

EventEmitter看起来是异步的,由于他老是被用来发出异步操做完成的信号。可是,EventEmitter API是彻底同步的。emit方法可能被异步调用,可是须要主要到所有的监听方法都是按照添加的顺序同步执行的。socket

 

总览async

Node自己依赖于不少的库。其中之一就是libuv。这个库就是用来处理队列和异步的事件的。Node极大限度的使用了操做系统核心已经有的功能。申请写操做,保持链接以及更多地由系统处理的功能。好比,链接申请被系统排队,直到被Node处理。

你也许了解过Node有一个线程池,也会想知道“若是node把这些职责都推掉了,那还须要什么线程池?” 这是由于系统核心并不支持什么事都异步执行。好比,有时node须要锁定某个线程,这样event loop能够一直执行而不至于死锁。

这里有一个简化的图来解释event loop是怎么运行的。

diagram

有一些event loop的内部执行机制很难在图中给出:

  • 全部使用process.nexTick()指定的回调都会在event loop的某阶段的最后时刻,在进入下一个阶段前被执行(好比,timer)。这样有一个潜在的风险,若是process.nextTrick()方法有递归调用的话,整个event loop就被拖死了。
  • “待处理队列(pending callbacks)”就是未被其余阶段(phase)处理的回调队列(好比:给fs.write()传进去的回调)。

 

Event Emitter和Event Loop

为了简化和Event loop的互操做,因此有了EventEmitter。用EventEmitter能够很容易建立一个基于事件的API。下面咱们就一些主要内容作讲解。

下面的代码展现了没有同步发出事件会形成用户错过事件的状况:

var EventEmitter = require('events').EventEmitter;
var util = require('util');

function MyThing() {
    EventEmitter.call(this);
    
    doFirstThing();
    this.emit('thing1');
}
util.inherites(MyThing, EventEmitter);

var mt = new MyThing();

mt.on('thing1', function(){
    // never going to happen.
});

以上代码的问题就在于‘thing1’永远不会被用户捕捉到,由于MyThing()必须在初始化完成以后才能监听事件。下面是一个简单地解决方案,不须要任何另外的闭包:

var EventEmitter = require('events').EventEmitter;
var util = require('util');

function MyThing() {
    EventEmitter.call(this);
    
    // doFirstThing();
    // this.emit('thing1');
    setImmediate(emitThing1, this);
}
util.inherits(MyThing, EventEmitter);

function emitThing1(self) {
    self.emit('thing1');
}

var mt = new MyThing();

mt.on('thing1', function(){
    // bravo
    console.log('bravo, thing1 captured.');
});

// bravo, thing1 captured.

下面的代码页能够运行,只不过消耗较多。

function MyThing() {
    EventEmitter.call(this);
    
    // doFirstThing();
    // this.emit('thing1');
    // setImmediate(emitThing1, this);
    setImmediate(this.emit.bind(this, 'thing1'));
}

另外一种状况是触发错误。查出你的应用的问题很是麻烦,并且若是没有调用栈的话,简直没法排查。一个Error在异步执行的深处初始化的时候可能会致使调用栈丢失。解决这个问题最靠谱的两个办法就是同步emit事件,或者确保Error带了足够的相关信息。请看如下代码的演示:

MyThing.prototype.foo = function () {
    var er = doFirstThing();
    if (er) {
        // emit error asynchronously
        setImmediate(emitError, this, new Error('Bad stuff'));
        return;
    }
    
    // emit error synchronously
    var er = doSecondThing();
    if (er) {
        this.emit('error', 'More bad stuff');
        return;
    }
};

emit的错误应该当即被处理,以防程序继续执行。并且在构造函数中emit错误也不是个好主意。

 

最后

本文只介绍了event loop知识的一小部分。这些会在以后的文章中补足。可是,这是继续下去以前必备的基础。以后的文章会讲述event loop是怎么和系统的核心互相交互的。

相关文章
相关标签/搜索