node 事件機制
一 三種定時器
NodeJS中有三種類型的定時器:超時時間、時間間隔、即時定時器
1.超時時間:setTimeout(callback,delayMilliSeconds,[args])方法,如:
setTimeout(myFunc,1000);
setTimeout函數返回一個定時器對象的ID,可以在delayMilliSeconds到期前的任何時候把此ID傳遞給clearTimeOut()來取消時間函數。
var myTimeout=setTimeout(myFunc,1000); ... clearTimeOut(myTimeout);
2.時間間隔
var myInterval=setInterval(myFunc,1000); ... clearInterval(myInterval);
3.即時器立即執行工作
即時計時器用來在I/O事件的回調函數開始執行后,但任何超時時間或時間間隔事件被執行之前,立即執行。
var myImmediate=setImmediate(myFunc,1000); ... clearImmediate(myImmediate);
二 事件發射器和監聽器
1.NodeJS事件模型的工作原理。
NodeJS不是在各個線程為每個請求執行所有的工作,它是把工作添加到事件隊列中,然后有一個單獨的線程運行一個事件循環把這個工作提取出來。事件循環抓取事件隊列中最上面的條目,執行它,然后抓取下一個條目。當執行到長期運行或有阻塞I/O的代碼時,它不是直接調用該函數,而是把函數雖同一個要在此函數完成后執行的回調函數一起添加到事件隊列中。當NodeJS事件隊列中的所有事件都被執行完成時,nodejs應用程序終止。
Node.js中,所有異步的I/O操作,在完成的時候都會發送一個事件到事件隊列中。
Node.js中的許多對象也都會分發事件,比如:net.Server 對象會在每次有新鏈接時分發一個事件;fs.readStream 對象會在文件被打開的時候分發一個事件等等,所有這些產生事件的對象都是event.EventEmitter(事件監聽/發射器)的實例。我們可以通過“ require('events') ”來訪問該模塊。
阻塞I/O停止當前線程的執行並等待一個回應,直到收到回應才能繼續。nodejs使用事件回調來避免對阻塞I/O的等待。事件回調的關鍵就是事件輪詢。
2.注冊並發射自定義node.js事件
事件使用一個EventEmitter對象來發出,這個對象包含在events模塊中,emit(eventName,[args])函數觸發eventName事件,包括所提供的任何參數。
var EventEmitter = require('events').EventEmitter; // 引入事件模塊 var event = new EventEmitter(); // 實例化事件模塊 // 注冊事件(customer_event) event.on('customer_event', function() { console.log('customer_event has be occured : ' + new Date()); }); setInterval(function() { event.emit('customer_event'); // 發射(觸發)事件 }, 500);
使用EventEmitter對象的on注冊事件,然后使用對象的emit發射事件。
3.EventEmitter介紹
events模塊只提供了一個對象:events.EventEmitter。EventEmitter的核心就是事件發射與事件監聽器功能的封裝。
var EventEmitter = require('events').EventEmitter; // 引入事件模塊 var event = new EventEmitter(); // 實例化事件模塊 // 注冊事件(sayHello) event.on('sayHello', function(param1, param2) { console.log('Hello1 : ', param1, param2); }); // 再次注冊事件(sayHello) event.on('sayHello', function(param1, param2) { console.log('Hello2 : ', param1, param2); }); event.emit('sayHello', 'GuYing', '1996'); // 發射(觸發)事件(sayHello)
注意到sayHello這個事件注冊了兩次。

EventEmitter的每一個事件都是由一個事件名和若干個參數組成。事件名是一個字符串,通常表達一定的語義。對於每個事件,EventEmitter支持若干個事件監聽器。當事件發射時,注冊到這個事件的事件監聽器被依次調用,事件參數作為回調函數參數傳遞。
運行結果中可以看到兩個事件監聽器回調函數被先后調用。 這就是EventEmitter最簡單的用法。
EventEmitter常用的API:
EventEmitter.on(event,listener)、emitter.addListener(event,listener)為指定事件注冊一個監聽器,接受一個字符串event和一個回調函數listener。
EventEmitter.once(event, listener) 為指定事件注冊一個單次監聽器,即監聽器最多只會觸發一次,觸發后立刻解除該監聽器。
EventEmitter.emit(event, [arg1], [arg2], [...]) 發射 event 事件,傳遞若干可選參數到事件監聽器的參數表。
EventEmitter.removeListener(event, listener) 移除指定事件的某個監聽器,listener必須是該事件已經注冊過的監聽器。
emitter.listeners(event) 返回這個事件的監聽函數的數組
emitter.setMaxListeners(n) 設置這個emitter實例的最大事件監聽數,默認是10個,設置0為不限制
emitter.removeAllListeners(event) 刪除所有對這個事件的監聽函數
舉個簡單的例子:
UserBean.js
var events=require('events'); var http=require('http'); function UserBean(){ //實例化事件模型 this.eventEmit=new events.EventEmitter(); this.zhuce=function(req,res){ console.log('注冊'); req['uname']='aa'; req['pwd']='bb'; //觸發事件 this.eventEmit.emit('zhuceSuccess','aa','bb'); }, this.login=function(req,res){ console.log('登錄'); res.write('用戶名:'+req['uname']); res.write('密碼:'+req['pwd']); res.write("登錄"); } } module.exports=UserBean;
event.js
var http=require('http'); var UserBean=require('./UserBean'); http.createServer(function(request,response){ response.writeHead(200,{'Content-Type':'text/html;charset=utf-8'}); if(request.url!=='favicon.ico'){ user=new UserBean(); user.eventEmit.once('zhuceSuccess',function(uname,pwd){ response.write('注冊成功'); console.log('傳uname '+uname); console.log('傳pwd '+pwd); user.login(request,response); response.end(); }); user.zhuce(request,response); } }).listen(8000); console.log('server running at http://127.0.0.1:8000/');
4.error事件
EventEmitter 定義了一個特殊的事件 error,它包含了"錯誤"的語義,我們在遇到 異常的時候通常會發射 error 事件。
當 error 被發射時,EventEmitter 規定如果沒有響應的監聽器,Node.js 會把它當作異常,退出程序並打印調用棧。
我們一般要為會發射 error 事件的對象設置監聽器,避免遇到錯誤后整個程序崩潰。例如:
var events=require('events'); var myEvent = new events.EventEmitter(); myEvent.emit('error', new Error('whoops!'));
運行時會顯示以下錯誤:

但是如果這么寫就可以正確報錯:
var events=require('events'); myEvent.on('error', (err) => { console.log('whoops! there was an error'); }); myEvent.emit('error', new Error('whoops!'));
5.注意
大多數時候我們不會直接使用 EventEmitter,而是在對象中繼承它。包括 fs、net、 http 在內的,只要是支持事件響應的核心模塊都是 EventEmitter 的子類。
為什么要這樣做呢?原因有兩點:
1)具有某個實體功能的對象實現事件符合語義, 事件的監聽和發射應該是一個對象的方法。
2)JavaScript 的對象機制是基於原型的,支持 部分多重繼承,繼承 EventEmitter 不會打亂對象原有的繼承關系。
Events(事件)模塊是Node.js的核心,許多其他模塊用它來圍繞着事件架構功能。由於Node.js運行在單一的線程中,任何同步代碼都是阻塞的,所以如果有長時間運行的代碼的話事件循環便會被阻塞。為了有效地使用Node.js編寫代碼,必須仔細思考自己的變成風格並遵循一些簡單的規則。
那么,如何將事件添加到你的javascript對象中呢?首先需要通過在對象實例中調用events.EventEmitter.call(this)來在對象中繼承EventEmitter功能,還需要把events.EventEmitter.prototype添加到對象的原型中,如:
function myObj(){ Events.EventEmitter.call(this); } myObj.prototype._proto_=evnets.EventEmitter.prototype;
然后從對象實例中發出事件:
var newObj=new myObj(); newObj.emit('someEvent');
舉個簡單的例子:
var events = require('events'); function Account() { this.balance = 0; events.EventEmitter.call(this); this.deposit = function(amount){ this.balance += amount; this.emit('balanceChanged'); }; this.withdraw = function(amount){ this.balance -= amount; this.emit('balanceChanged'); }; } Account.prototype.__proto__ = events.EventEmitter.prototype; function displayBalance(){ console.log("Account balance: $%d", this.balance); } function checkOverdraw(){ if (this.balance < 0){ console.log("Account overdrawn!!!"); } } function checkGoal(acc, goal){ if (acc.balance > goal){ console.log("Goal Achieved!!!"); } } var account = new Account(); account.on("balanceChanged", displayBalance); account.on("balanceChanged", checkOverdraw); account.on("balanceChanged", function(){ checkGoal(this, 1000); }); account.deposit(220); account.deposit(320); account.deposit(600); account.withdraw(1200);
