[Nodejs]事件機制(EventEmitter)

以往我們在設計程式規畫流程時,總是會有些函數是必須等待的,等待他的結束或是等待他的某項任務處理結束。

等待時,最常用的方法就是讓他在同一個thread裡面,函數執行結束後理所當然的就會執行下一步驟。若是用不同thread處理時就無法等待,可能就會用一個旗標(flag)去判斷繼續執行或等待,不過這樣的等待會造成整個thread block住(busy loop),不僅浪費thread資源,也浪費CPU的效能。

事件觸發這個想法也不是從nodejs開始的,很早之前在JAVA、C#和前端javascript就已經開始使用了。但在nodejs中這會是一個絕佳的利器,因為你沒辦法擁有多執行緒,也無法完整的控管已經被分配出去抓不到的非同步函數。

作業系統:linux/windows
版本:nodejs v4.X
主要模組: events
輔助套件: 
readline - 講解用,實際上使用並不會用到
util - 繼承用,若物件需要被賦予事件的功能時會用到

名詞

callback/cb - 回呼函數,在註冊事件時,傳入的function,以便事件觸發時將值傳入並執行 
emit/trigger - 同樣都是觸發的意思,在不同套件中可能會是其中一個,或是兩個都有。

範例:從console中讀取字串

在C語言裡面我們從用gets或是scanf,但是在執行下去之後C程式會block住,等待使用者輸入訊息。好一點的方法會請kernal去監聽STDIN串流(stream),有值讀才去讀,或是用一個thread去等待,進而不影響到main的執行。

在nodejs中方法和請kernal去監聽有點像,當使用者有輸入訊息時才去處理任務。

const readline = require('readline');




const rl = readline.createInterface({


    input: process.stdin,


    output: process.stdout


});




rl.on("line", function(line) {


    console.log(line);


});


當rl發生 line 事件時,做第二個參數的事情。事件發生時,是由event emitter替你執行,將值丟入你所給的callback函數中,所以實作event時要對照模組的相關api,才能知道收到的參數是什麼東西。

事件發生的時候,function內部的this會是觸發事件的event emitter,如果你是用繼承的方式,那麼就會是觸發事件的那個object。

註:本篇主要講解event,若對 readline模組有疑問可以看官方說明。

實作event emitter
 var EVENT_CLASS = require("events");
 var event_object = new EVENT_CLASS();

 event_object.on("event name", function(message) {
     console.log("event:event name trigger");
     console.log(message);
 });

 event_object.emit("event name", "Hello World");

 /**
  * output:
  * "event:event name trigger"
  * "Hello World"
  */


event name是自己定義的,想要叫什麼名字都行,想要監聽多少事件都行,要注意的是多少人去監聽(on)是有限制的,預設值10,如果超過了,程式會丟出例外。



on是監聽事件,第二個參數是發生事件時所要執行的callback。

emit是觸發事件,在發生相關事情的時候主動執行,如果該物件有被監聽觸發的事件,就會將第二個參數之後傳入監聽的callback函數裡頭。

繼承event emitter

如果只是將event emitter實作出來會有點像是從第三方去控管事件,不過當物件多起來的時候這樣會變得很複雜,相同的事件名稱處發在不同的物件上面,很容易造成混淆。

因此你也許希望,每個物件都有自己的事件可以觸發和監聽,那你就必須要到繼承了。

註:詳細的class實作將會在[Nodejs]函數/類別(function)

 var EVENT_CLASS = require("events");

 var util = require('util');

 var object_class = function() {
     this.name = "Shach";
     this.url = "http://shachkuo.blogspot.tw/";
     EVENT_CLASS.call(this);
 };

 util.inherits(object_class, EVENT_CLASS);

 object_class.prototype.show = function() {
     console.log("hello " + this.name + ", your blogger url:" + this.url);
 };


 object_class.prototype.set_url = function(url) {
     if (url) {
         if (url !== this.url) {
             this.url = url;
             this.emit("change");
         }
     }
 };

 var event_object = new object_class();

 event_object.on("change", function() {
     console.log("object had change");
 });

 event_object.show();
 event_object.set_url("test change");
 event_object.show();

 /**
  * output:
  * hello Shach, your blogger url:http://shachkuo.blogspot.tw/
  * object had change
  * hello Shach, your blogger url:test change
  */


以上提供的是從官方正統的物件繼承事件功能,不過這種方法Shach本身基本上是完全沒用到,未來會再寫一篇運用於後端的[Nodejs]Backbone(Model/View/Collection),到時在跟大家討論我自己本身如何使用。

寫到這裡已經覺得自己好像在翻譯官方網站了...其實官方寫的好詳細!

可用api

監聽事件:
     // 監聽觸發(兩者相同)
     event_object.addListener({string event_name}, {function callback});
     event_object.on({string event_name}, {function callback});
     // 只監聽一次觸發
     event_object.once({string event_name}, {function callback});

觸發事件:
    event_object.emit({string event_name}, {any_type argument}, {any_type argument}, {any_type argument}, ...);

取得/設訂/查詢:
    event_object.getMaxListeners()
    event_object.setMaxListeners(n)
    event_object.listenerCount(event)
    event_object.listeners(event)
    

移除監聽事件:
    // 有輸入event name則移除該event所有監聽的callback
    // 沒有輸入則移出該object所有事件callback
    event_object.removeAllListeners([event])
    event_object.removeListener(event, listener)
    

    

沒有留言:

張貼留言