亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

Node.js中使用事件發射器模式實現事件綁定詳解

2019-11-20 14:16:07
字體:
來源:轉載
供稿:網友

在Node里,很多對象都會發射事件。比如,一個TCP服務器,每當有客戶端請求連接就會發射“connect”事件,又比如,每當讀取一整塊數據,文件系統就會發射一個“data”事件。這些對象在Node里被稱為事件發射器(event emitter)。事件發射器允許程序員訂閱他們感興趣的事件,并將回調函數綁定到相關的事件上,這樣每當事件發射器發射事件時回調函數就會被調用。發布/訂閱模式非常類似傳統的GUI模式,比如按鈕被點擊時程序就會收到相應的通知。使用這種模式,服務端程序可以在一些事件發生時作出反應,比如有客戶端連接,socket上有可用數據,或者文件被關閉的時候。

還可以創建自己的事件發射器,事實上,Node專門提供了一個EventEmitter偽類,可以把它當作基類來創建自己的事件發射器。

理解回調模式

異步編程不使用函數返回值來表明函數調用的結束,而是采用后繼傳遞風格。

“后繼傳遞風格”(CPS:Continuation-passing style)是一種編程風格,流程控制被顯式傳遞給下一步操作……

CPS風格的函數會接受一個函數作為額外參數,這個函數用來顯式指出程序控制的下個流程,當CPS函數計算出它的“返回值”,它就會調用那個代表了程序下個流程的函數,并將CPS函數的“返回值”作為其參數。

出自維基百科――http://en.wikipedia.org/wiki/Continuation-passing_style

這種編程風格里,每個函數在執行結束后都會調用一個回調函數,這樣程序就可以繼續運行。后面你會明白,JavaScript非常適合這種編程風格,下面是個Node下將文件加載到內存的例子:

復制代碼 代碼如下:

var fs = require('fs');

fs.readFile('/etc/passwd', function(err, fileContent) {

    if (err) {

        throw err;

    }

    console.log('file content', fileContent.toString());

});

這個例子里,你傳遞了一個內聯匿名函數作為fs.readFile的第二個參數,其實這就是在使用CPS編程,因為你把程序執行的后續流程交給了那個回調函數。

如你所見,回調函數的第一個參數是個錯誤對象,如果程序發生錯誤,這個參數將會是一個Error類的實例,這是Node里CPS編程的一個常見模式。

理解事件發射器模式

標準回調模式里,把一個函數作為參數傳遞給將被執行的函數,這種模式在客戶端需要在函數完成后被通知的場景下工作的很好。但是如果函數的執行過程中發生了多個事件或事件重復發生了多次,這種模式就不太適合了。比如,你想在socket每次收到可用數據時得到通知,這種場景你會發現標準回調模式不太好用,這時事件發射器模式就派上用場了,你可以用一套標準接口來清晰的分離事件發生器和事件監聽器。

使用事件發生器模式時,會涉及到兩個或多個對象――事件發射器和一個或多個事件監聽器。

事件發射器,顧名思義,是個可以產生事件的對象。而事件監聽器則是綁定到事件發射器上的代碼,用來監聽特定類型的事件,就像下面的例子:

復制代碼 代碼如下:

var req = http.request(options, function(response) {

    response.on("data", function(data) {

        console.log("some data from the response", data);

    });

    response.on("end", function() {

         console.log("response ended");

    });

});

req.end();

這段代碼演示了用Node的 http.request API(見后面章節)創建一個HTTP請求來訪問遠程HTTP服務器時的兩個必要步驟。第一行采用了“后繼傳遞風格”(CPS:Continuation-passing style),傳遞了一個當HTTP響應時會被調用的內聯函數。HTTP請求API在這兒使用CPS是因為程序需要在http.request函數執行完畢后才繼續執行后續操作。

當http.request執行完畢,就會調用那個匿名回調函數,然后將HTTP響應對象作為參數傳遞給它,這個HTTP響應對象是個事件發射器,根據Node文檔,它可以發射包括data,end在內的很多事件,你注冊的那些回調函數會在每次事件發生時被調用。

作為一條經驗,當你需要在請求的操作完成后重新獲取執行權時使用CPS模式,以及當事件可以發生多次時使用事件發射器模式。

理解事件類型

被發射的事件都有一個用字符串表示的類型,前面的例子包含“data”和“end”兩個事件類型,它們是由事件發射器來定義的任意字符串,不過約定俗成的是,事件類型通常都由不包含空字符的小寫單詞組成。

不能用代碼來推斷出事件發射器能產生哪些類型的事件,因為事件發射器API并沒有內省機制,因此你使用的API應該有文檔來表明它能發射那些類型的事件。

一旦事件發生,事件發射器就會調用跟事件相關的監聽器,并將相關數據作為參數傳遞給監聽器。在前面http.request那個例子里,“data”事件回調函數接受一個data對象作為它第一個也是唯一的參數,而“end”不接受任何數據,這些參數作為API契約的一部分也是由API的作者主觀定義的,這些回調函數的參數簽名也會在每個事件發射器的API文檔里有說明。

事件發射器雖然是個為所有類型事件服務的接口,不過“error”事件是Node里的一個特殊實現。Node里的大多數事件發射器都會在程序發生錯誤時產生“error”事件,如果程序沒有監聽某個事件發射器的 “error”事件,事件發射器將會注意到并在錯誤發生時向上拋出一個未捕獲異常。

你可以在Node PERL里運行下面的代碼來測試下效果,它模擬了一個能產生兩種事件的事件發射器:

復制代碼 代碼如下:

var em = new (require('events').EventEmitter)();

em.emit('event1');

em.emit('error', new Error('My mistake'));

你將會看到下面的輸出:

復制代碼 代碼如下:

var em = new (require('events').EventEmitter)();

undefined

> em.emit('event1');

false

> em.emit('error', new Error('My mistake'));

Error: My mistake

at repl:1:18

at REPLServer.eval (repl.js:80:21)

at repl.js:190:20

at REPLServer.eval (repl.js:87:5)

at Interface.<anonymous> (repl.js:182:12)

at Interface.emit (events.js:67:17)

at Interface._onLine (readline.js:162:10)

at Interface._line (readline.js:426:8)

at Interface._ttyWrite (readline.js:603:14)

at ReadStream.<anonymous> (readline.js:82:12)

>

代碼第2行,隨便發射了一個叫“event1”的事件,沒有任何效果,但是當發射“error”事件時,錯誤被拋出到堆棧。如果程序不是運行在PERL命令行環境里,程序將會因為未捕獲的異常而崩潰。

使用事件發射器API

任何實現了事件發射器模式的對象(比如TCP Socket,HTTP 請求等)都實現了下面的一組方法:

復制代碼 代碼如下:

.addListener和.on ―― 為指定類型的事件添加事件監聽器
.once ―― 為指定類型的事件綁定一個僅執行一次的事件監聽器
.removeEventListener ―― 刪除綁定到指定事件上的某個監聽器
.removeAllEventListener ―― 刪除綁定到指定事件上的所有監聽器

下面我們具體介紹它們。

使用.addListener()或.on()綁定回調函數

通過指定事件類型和回調函數,你可以注冊當事件發生時被執行的操作。比如,文件讀取數據流時如果有可用的數據塊,就會發射一個“data”事件,下面代碼展示如何通過傳入一個回調函數來讓程序告訴你發生了data事件。

復制代碼 代碼如下:

function receiveData(data) {

   console.log("got data from file read stream: %j", data);

}

readStream.addListener(“data”, receiveData);

你也可以使用.on,它只是.addListener的簡寫方式,下面的代碼和上面的是一樣的:

復制代碼 代碼如下:

function receiveData(data) {

   console.log("got data from file read stream: %j", data);

}
readStream.on(“data”, receiveData);

前面代碼,使用事先定義的一個的命名函數作為回調函數,你也可以使用一個內聯匿名函數來簡化代碼:

復制代碼 代碼如下:

readStream.on("data", function(data) {

   console.log("got data from file read stream: %j", data);

});


前面說過,傳遞給回調函數的參數個數和簽名依賴于具體的事件發射器對象和事件類型,它們并不是被標準化的,“data”事件可能傳遞的是一個數據緩沖對象,“error”事件傳遞一個錯誤對象,數據流的“end”事件不向事件監聽器傳遞任何數據。

綁定多個事件監聽器

事件發射器模式允許多個事件監聽器監聽同一個事件發射器的同一事件類型,比如:

復制代碼 代碼如下:

I have some data here.

I have some data here too.

事件發射器負責按監聽器的注冊順序調用指定事件類型上綁定的所有監聽器,也就是說:

1.當事件發生后事件監聽器可能不會被立刻調用,也許會有其它事件監聽器在它之前被調用。
2.異常被拋出到堆棧是不正常的行為,可能是因為代碼里有bug,當事件被發射時,如果有一個事件監聽器在被調用時拋出了異常,可能會導致一些事件監聽器永遠不會被調用。這種情況下,事件發射器會捕獲到異常,也許還會處理它。

看下面這個例子:

復制代碼 代碼如下:

readStream.on("data", function(data) {

   throw new Error("Something wrong has happened");

});

readStream.on("data", function(data) {

   console.log('I have some data here too.');

});

因為第一個監聽器拋出了異常,因此第二個監聽器不會被調用。

用.removeListener()從事件發射器移除一個事件監聽器

如果當你不再關心一個對象的某個事件時,你可以通過指定事件類型和回調函數來取消已注冊的事件監聽器,像這樣:

復制代碼 代碼如下:

function receiveData(data) {

    console.log("got data from file read stream: %j", data);

}

readStream.on("data", receiveData);

// ...

readStream.removeListener("data", receiveData);

這個例子里,最后一行把一個可能在將來被隨時調用的事件監聽器從事件發射器對象移除了。

為了刪除監聽器,你必須給回調函數命名,因為在添加和刪除的時候需要回調函數的名字。

使用.once()讓回調函數最多執行一次

如果你想監聽一個最多執行一次的事件,或者只對某個事件發生的第一次感興趣,可以用.once()函數:

復制代碼 代碼如下:

function receiveData(data) {

    console.log("got data from file read stream: %j", data);

}

readStream.once("data", receiveData);

上面的代碼,receiveData函數只會被調用一次。如果readStream對象發射了data事件,receiveData回調函數將會而且僅會被觸發一次。

它其實只是個方便方法,因為很簡單的就能實現它,像這樣:

復制代碼 代碼如下:

var EventEmitter = require("events").EventEmitter;

EventEmitter.prototype.once = function(type, callback) {

   var that = this;

   this.on(type, function listener() {

      that.removeListener(type, listener);

      callback.apply(that, arguments);

   });

};

上面代碼里,你重新定了EventEmitter.prototype.once函數,同時也重定義了每個繼承自EventEmitter的所有對象的once函數。代碼只是簡單的使用.on()方法,一旦收到了事件,就用.removeEventListener()取消回調函數的注冊,并調用原來的回調函數。

注意:前面代碼里使用了function.apply()方法,它接受一個對象并把它作為內含的this變量,以及一個參數數組。前面例子里,通過事件發射器把未修改過的參數數組透明地傳遞給回調函數。

用.removeAllListeners()從事件發射器移除所有事件監聽器

你可以像下面那樣從事件發射器移除所有注冊到指定事件類型上的所有監聽器:

復制代碼 代碼如下:

emitter.removeAllListeners(type);

比如,你可以這樣取消所有進程中斷信號的監聽器:

復制代碼 代碼如下:

process.removeAllListeners("SIGTERM");

注意:作為一條經驗,推薦你只在確切知道刪除了什么內容時才使用這個函數,否則,應該讓應用程序其它部分來刪除事件監聽器集合,或者也可以讓程序的那些部分自己負責移除監聽器。但不管怎樣,在某些罕見的場景下,這個函數還是很有用的,比如當你準備有序的關閉一個事件發射器或者關閉整個進程的時候。

創建事件發射器

事件發射器用一個很棒的方式讓編程接口變得更通用,在一個常見易懂的編程模式里,客戶端直接調用各種函數,而在事件發射器模式中,客戶端被綁定到各種事件上,這會讓你的程序變得更靈活。(譯者注:這句不太自信,貼出原文:The event emitter provides a great way of making a programming interface more generic. When you use a common understood pattern, clients bind to events instead of invoking functions, making your program more flexible.)

此外,通過使用事件發射器,你還可以獲得許多特性,比如在同一事件上綁定多個互不相關的監聽器。

從Node事件發射器繼承

如果你對Node的事件發射器模式感興趣,并打算用到自己的應用程序里,你可以通過繼承EventEmitter來創建一個偽類:

復制代碼 代碼如下:

util = require('util');

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

// 這是MyClass的構造函數:

var MyClass = function() {

}

util.inherits(MyClass, EventEmitter);

注意:util.inherits建立了MyClass的原形鏈,讓你的MyClass實例可以使用EventEmitter的原形方法。

發射事件

通過繼承自EventEmitter,MyClass可以像這樣發射事件了:

復制代碼 代碼如下:

MyClass.prototype.someMethod = function() {

    this.emit("custom event", "argument 1", "argument 2");

};

上面的代碼,當someMethond方法被MyClass的實例調用時,就會發射一個叫“cuteom event”的事件,這個事件還會發射兩個字符串作為數據:“argument 1”和“argument 2”,它們將會作為參數傳遞給事件監聽器。

MyClass實例的客戶端可以像這樣監聽“custom event”事件:

復制代碼 代碼如下:

var myInstance = new MyClass();

myInstance.on('custom event', function(str1, str2) {

    console.log('got a custom event with the str1 %s and str2 %s!', str1, str2);

});

再比如,你可以這樣創建一個每秒發射一次“tick”事件的Ticker類:

復制代碼 代碼如下:

var util = require('util'),

EventEmitter = require('events').EventEmitter;

var Ticker = function() {

    var self = this;

    setInterval(function() {

        self.emit('tick');

    }, 1000);

};

util.inherits(Ticker, EventEmitter);

用Ticker類的客戶端可以展示如何使用Ticker類和監聽“tick”事件,

復制代碼 代碼如下:

var ticker = new Ticker();

ticker.on("tick", function() {

    console.log("tick");

});

小結

事件發射器模式是種可重入模式(recurrent pattern),可以用它將事件發射器對象從一組特定事件的代碼中解耦合。

可以用event_emitter.on()來為特定類型的事件注冊監聽器,并用event_emitter.removeListener()來取消注冊。

還可以通過繼承EventEmitter和簡單的使用.emit()函數來創建自己的事件發射器。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
26uuu日韩精品一区二区| 在线播放国产一区二区三区| 国产精品爽黄69天堂a| 国产精品色午夜在线观看| www.精品av.com| 色婷婷久久一区二区| 3344国产精品免费看| 精品日本高清在线播放| 欧美电影在线观看网站| 亚洲国产一区自拍| 伊人久久久久久久久久久久久| 伊人伊成久久人综合网站| 欧美性猛交丰臀xxxxx网站| 亚洲人在线观看| 日韩精品在线看| 欧美老妇交乱视频| 欧美日韩午夜剧场| 欧洲s码亚洲m码精品一区| 精品毛片网大全| 久久精品成人一区二区三区| 亚洲国产高潮在线观看| 黑丝美女久久久| 中文字幕自拍vr一区二区三区| 成人黄色网免费| 国产一区二区三区久久精品| 亚洲va国产va天堂va久久| 精品久久久在线观看| 亚洲国产精彩中文乱码av| 精品高清一区二区三区| 亚洲人成绝费网站色www| 国产精品第一页在线| 欧美精品久久久久久久久| 久久久久久久一区二区| 国产精品扒开腿做爽爽爽视频| 91深夜福利视频| 久久五月情影视| 国产美女直播视频一区| 久久久欧美一区二区| 欧美一级淫片videoshd| 欧美电影免费观看大全| 国产视频999| 亚洲女人被黑人巨大进入al| 亚洲free嫩bbb| 国产suv精品一区二区三区88区| 欧美精品成人在线| 欧美激情综合色综合啪啪五月| 欧美精品一区在线播放| 色久欧美在线视频观看| 精品一区二区三区四区在线| 欧美成人激情视频免费观看| 亚洲精品第一页| 久久影院资源站| 国产午夜精品一区理论片飘花| 国产成人精品一区| 亚洲一区久久久| 欧美亚洲一级片| 久久久久久久色| 欧洲成人免费aa| 国产在线日韩在线| 国产精品高潮在线| 91精品中文在线| 国产精品无av码在线观看| 国产午夜精品美女视频明星a级| www.美女亚洲精品| 正在播放国产一区| 国产精品尤物福利片在线观看| 日本久久久久亚洲中字幕| 97精品久久久中文字幕免费| 亚洲欧美日本伦理| 韩国三级日本三级少妇99| 欧美成人中文字幕在线| 日本亚洲欧洲色α| 欧美日韩成人在线观看| 亚洲欧美一区二区三区在线| www日韩欧美| 亚洲永久免费观看| 一区二区三区动漫| xx视频.9999.com| 国产精品久久久久久久久影视| 亚洲成人精品av| 亚洲大胆美女视频| 九九热视频这里只有精品| 亚洲在线免费观看| 日韩人在线观看| 日韩免费观看在线观看| 日韩成人中文字幕在线观看| 国内精品一区二区三区| 久久99国产综合精品女同| 欧美孕妇孕交黑巨大网站| 国产精品美女久久久久av超清| 国产视频久久久久| 91av中文字幕| 自拍偷拍亚洲精品| 久久亚洲一区二区三区四区五区高| 4k岛国日韩精品**专区| 欧美日韩中国免费专区在线看| 亚洲精品中文字| 日本韩国在线不卡| 色999日韩欧美国产| 亚洲成人久久久久| 亚洲黄色www网站| 国产手机视频精品| 亚洲久久久久久久久久| 精品人伦一区二区三区蜜桃免费| 国产精品第8页| 久久久久久久久久久国产| 亚洲精品久久久久国产| 亚洲最新av在线| 日本久久中文字幕| 亚洲男人天堂网站| 成人欧美一区二区三区在线| 久久久免费精品| 国产精品美女免费视频| 亚洲国产成人在线播放| 国产精品亚洲综合天堂夜夜| 国产精品一二三在线| 另类天堂视频在线观看| 91亚洲精品一区| 91最新在线免费观看| 久久天天躁狠狠躁夜夜躁| 91精品国产91久久久| 人体精品一二三区| 一区二区三区视频免费在线观看| 96pao国产成视频永久免费| 欧美大片在线看| 亚洲国产精品资源| 亚洲精品国产成人| 中文字幕日韩在线观看| 国产精品国产亚洲伊人久久| 91亚洲精品在线| 日本一区二区不卡| 亚洲最大的成人网| 久久福利网址导航| 亚洲精品国精品久久99热| 国产精品99久久99久久久二8| 中文字幕成人精品久久不卡| 国产精品免费久久久久久| 成人动漫网站在线观看| 国产主播欧美精品| 成人看片人aa| 日韩成人av一区| 成人精品久久久| 国产日产亚洲精品| 欧美精品免费看| 91老司机精品视频| 亚洲欧美综合v| 亚洲国产日韩精品在线| 日韩成人中文字幕在线观看| 亚洲视频在线播放| 国产精品三级久久久久久电影| 色妞一区二区三区| 九九热这里只有精品6| 国产精品久久久久久久久久久久久久| 国产成人精品午夜| 91免费看国产| 欧美性受xxxx黑人猛交| 精品国产1区2区| 亚洲最大福利视频网| 国产精品∨欧美精品v日韩精品| 国产精品综合久久久| 97人人爽人人喊人人模波多| 中文字幕亚洲无线码在线一区| 成人黄色av网| 欧美专区在线播放|