EventEmitter class

在Node中锦爵,要實現(xiàn)觀察者模式非常的簡單,而且內(nèi)置于EventEmitter類中宛篇,EventEmitter類允許我們注冊一個或者多個函數(shù)作為監(jiān)聽者计福,當(dāng)對應(yīng)的事件觸發(fā)后跌捆,它們就會被觸發(fā)

64C16489-985B-4083-823C-0A426D4F3C68.png

EventEmitter是一個原型,可以通過events這個核心模塊獲取得到

const EventEmitter = require('events').EventEmitter;
const eventEmitter = new EventEmitter();

EventEmitter內(nèi)部提供了幾個API

  • on(event, listener) 注冊監(jiān)聽者
  • once(event, listener)注冊監(jiān)聽者象颖,但是只會觸發(fā)一次
  • emit(event, [arg1], [...]) 發(fā)布一個事件
  • removeListener(event, listener)移除監(jiān)聽者

上面的方法都支持鏈?zhǔn)秸{(diào)用佩厚。而且我們會發(fā)現(xiàn)listener和我們所知道的傳統(tǒng)的Node回調(diào)函數(shù)是不同的,最突出的就是函數(shù)的第一個參數(shù)不是error说订,而是我們emit時候穿過去的參數(shù)

使用EventEmitter

使用它最簡單的方式就是去創(chuàng)建一個新的實例抄瓦,然后立刻使用它

const EventEmitter = require('events').EventEmitter;
const fs = require('fs');

function findPattern(files, regex) {
    const emitter = new EventEmitter();
    files.forEach((file, index) => {
        fs.readFile(file, (err, content) => {
            if(err) {
                emitter.emit('error', err);
            }
            emitter.emit('fileRead', file);
            if(regex.test(content)) {
                emitter.emit('found', content);
            }
        })
    })
    return emitter;
}

findPattern(['a.js', 'b.js', 'main.js'], new RegExp("main", 'i'))
    .on('error', function(err) {
        console.log(err);
    })
    .on('fileRead', function(file) {
        console.log(file + ' read\n');
    })
    .on('found', function(content) {
        console.log(content.toString());
        console.log('\n');
    })

傳播錯誤

EventEmitter和回調(diào)函數(shù)一樣,當(dāng)異步事件發(fā)生錯誤的時候陶冷,直接拋出異常是無法被捕捉到的钙姊,因為它們所處的event loop是不相同的

const EventEmitter = require('events').EventEmitter;
const fs = require('fs');

function findPattern(files, regex) {
    const emitter = new EventEmitter();
    files.forEach((file, index) => {
        fs.readFile(file, (err, content) => {
            if(err) {
                // emitter.emit('error', err);
                throw new Error(err);
            }
            emitter.emit('fileRead', file);
            if(regex.test(content)) {
                emitter.emit('found', content);
            }
        })
    })
    return emitter;
}

findPattern(['a.js', 'b.js', 'main.js'], new RegExp("main", 'i'))
    .on('error', function(err) {
        console.log(err);
    })
    .on('fileRead', function(file) {
        console.log(file + ' read\n');
    })
    .on('found', function(content) {
        console.log(content.toString());
        console.log('\n');
    })

所以我們平時在寫代碼的時候,要注意埂伦,多增加一個關(guān)于error的事件進(jìn)行監(jiān)聽煞额,然后進(jìn)一步處理

使任何對象可觀察

有些時候,我們直接通過EventEmitter去創(chuàng)建一個可觀察對象是不夠的沾谜,因為它不能夠為我們提供擴展的功能膊毁,所以更多的時候,我們通過繼承EventEmitter這個類类早,去創(chuàng)建一個更具有擴展性的可觀察的對象

為了進(jìn)一步描述這種方法媚媒,我們通過重寫上面的findPattern來說明

const fs = require('fs');
const EventEmitter = require('events').EventEmitter;

class FindPattern extends EventEmitter {
    constructor(regex) {
        super();
        this.regex = regex;
        this.files = [];
    }

    addFile(file) {
        this.files.push(file);
        return this;
    }

    find() {
        this.files.forEach((file, index) => {
            fs.readFile(file, (err, content) => {
                if(err) {
                    this.emit('error', err);
                }
                this.emit('fileRead', file);
                if(this.regex.test(content)) {
                    this.emit('found', content);
                }
            })
        })
    }
}



const interface = new FindPattern(new RegExp("main"));

interface.addFile('a.js')
    .addFile('b.js')
    .addFile('main.js')
    .on('error', function(err) {
        console.log(err);
    })
    .on('fileRead', function(file) {
        console.log(file + ' read\n');
    })
    .on('found', function(content) {
        console.log(content.toString());
        console.log('\n');
    })
    .find()

這種模式在Node的生態(tài)圈里十分常見嗜逻,HTTP涩僻、TCP等模塊里都大量了使用了這種模式

同步和異步事件

和回調(diào)函數(shù)一樣,事件是可以同步觸發(fā),也可以是異步觸發(fā)的逆日,但是強調(diào)的是嵌巷,在同一個EventEmitter內(nèi),不能混用兩種模式

emit同步事件和異步事件最主要的區(qū)別取決于注冊listener的方法室抽,如果events是異步的emit搪哪,那么程序有充足的時間去注冊listener,因為event不會在本次event loop被觸發(fā)

如果events是同步的被emit坪圾,那么就需要在emit之前去注冊listener

class SycnEmitter extends EventEmitter {
    constructor() {
        super();
        this.emit('ready', 'ready');
    }
}

const interface = new SycnEmitter();

interface.on('ready', (ready) => {
    console.log(ready);
})

上面的代碼晓折,就是因為沒有在emit之前進(jìn)行注冊,導(dǎo)致沒有任何的輸出結(jié)果兽泄,如果是異步的去emit的話漓概,就不會這樣。

所以我們在使用EventEmitter的時候病梢,需要考慮好使用的場景胃珍,再根據(jù)場景決定使用同步的方式還是異步的方式

EventEmitter VS callbacks

在定義異步API時,常見的難點是檢查是否使用EventEmitter的事件機制或僅接受回調(diào)函數(shù)蜓陌。一般區(qū)分規(guī)則是這樣的:當(dāng)一個結(jié)果必須以異步方式返回時觅彰,應(yīng)該使用回調(diào)函數(shù),當(dāng)需要結(jié)果不確定其方式時钮热,應(yīng)該使用事件機制來響應(yīng)填抬。

但是,由于這兩者實在太相近隧期,并且可能兩種方式都能實現(xiàn)相同的應(yīng)用場景痴奏,所以產(chǎn)生了許多混亂。以下列代碼為例:

function helloEvents() {
  const eventEmitter = new EventEmitter();
  setTimeout(() => eventEmitter.emit('hello', 'hello world'), 100);
  return eventEmitter;
}

function helloCallback(callback) {
  setTimeout(() => callback('hello world'), 100);
}

helloEvents()helloCallback()在其功能上可以被認(rèn)為是等價的厌秒,第一個使用事件機制實現(xiàn)读拆,第二個則使用回調(diào)來通知調(diào)用者,而將事件作為參數(shù)傳遞鸵闪。但是真正區(qū)分它們的是可執(zhí)行性檐晕,語義和要實現(xiàn)或使用的代碼量。雖然我們不能給出一套確定性的規(guī)則來選擇一種風(fēng)格蚌讼,但我們當(dāng)然可以提供一些提示來幫助你做出決定辟灰。

相比于第一個例子篡石,即觀察者模式而言芥喇,回調(diào)函數(shù)在支持不同類型的事件時有一些限制凰萨。但是事實上械馆,我們?nèi)匀豢梢酝ㄟ^將事件類型作為回調(diào)的參數(shù)傳遞,或者通過接受多個回調(diào)來區(qū)分多個事件武通。然而霹崎,這樣做的話不能被認(rèn)為是一個優(yōu)雅的API冶忱。在這種情況下,EventEmitter可以提供更好的接口和更精簡的代碼囚枪。

EventEmitter更優(yōu)秀的另一種應(yīng)用場景是多次觸發(fā)同一事件或不觸發(fā)事件的情況派诬。事實上,無論操作是否成功链沼,一個回調(diào)預(yù)計都只會被調(diào)用一次千埃。但有一種特殊情況是忆植,我們可能不知道事件在哪個時間點觸發(fā),在這種情況下朝刊,EventEmitter是首選拾氓。

最后冯挎,使用回調(diào)的API僅通知特定的回調(diào),但是使用EventEmitter函數(shù)可以讓多個監(jiān)聽器都接收到通知房官。

在定義異步API時续滋,常見的難點是檢查是否使用EventEmitter的事件機制或僅接受回調(diào)函數(shù)。一般區(qū)分規(guī)則是這樣的:當(dāng)一個結(jié)果必須以異步方式返回時疲酌,應(yīng)該使用回調(diào)函數(shù),當(dāng)需要結(jié)果不確定其方式時湿颅,應(yīng)該使用事件機制來響應(yīng)粥诫。

但是,由于這兩者實在太相近怀浆,并且可能兩種方式都能實現(xiàn)相同的應(yīng)用場景怕享,所以產(chǎn)生了許多混亂秒啦。以下列代碼為例:

function helloEvents() {
  const eventEmitter = new EventEmitter();
  setTimeout(() => eventEmitter.emit('hello', 'hello world'), 100);
  return eventEmitter;
}

function helloCallback(callback) {
  setTimeout(() => callback('hello world'), 100);
}

helloEvents()helloCallback()在其功能上可以被認(rèn)為是等價的搀玖,第一個使用事件機制實現(xiàn),第二個則使用回調(diào)來通知調(diào)用者芳来,而將事件作為參數(shù)傳遞猜拾。但是真正區(qū)分它們的是可執(zhí)行性,語義和要實現(xiàn)或使用的代碼量挎袜。雖然我們不能給出一套確定性的規(guī)則來選擇一種風(fēng)格,但我們當(dāng)然可以提供一些提示來幫助你做出決定紊搪。

相比于第一個例子全景,即觀察者模式而言,回調(diào)函數(shù)在支持不同類型的事件時有一些限制爸黄。但是事實上,我們?nèi)匀豢梢酝ㄟ^將事件類型作為回調(diào)的參數(shù)傳遞梆奈,或者通過接受多個回調(diào)來區(qū)分多個事件称开。然而,這樣做的話不能被認(rèn)為是一個優(yōu)雅的API径荔。在這種情況下脆霎,EventEmitter可以提供更好的接口和更精簡的代碼。

EventEmitter更優(yōu)秀的另一種應(yīng)用場景是多次觸發(fā)同一事件或不觸發(fā)事件的情況睛蛛。事實上胧谈,無論操作是否成功荸频,一個回調(diào)預(yù)計都只會被調(diào)用一次。但有一種特殊情況是稳强,我們可能不知道事件在哪個時間點觸發(fā)和悦,在這種情況下,EventEmitter是首選褒繁。

總結(jié)

最后馍忽,使用回調(diào)的API僅通知特定的回調(diào),但是使用EventEmitter函數(shù)可以讓多個監(jiān)聽器都接收到通知遭笋。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末坐梯,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子吵血,更是在濱河造成了極大的恐慌芝囤,老刑警劉巖措伐,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件秩命,死亡現(xiàn)場離奇詭異弃锐,居然都是意外死亡,警方通過查閱死者的電腦和手機霹菊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進(jìn)店門支竹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鸠按,“玉大人饶碘,你說我怎么就攤上這事扎运。” “怎么了绪囱?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵鬼吵,是天一觀的道長篮赢。 經(jīng)常有香客問我齿椅,道長启泣,這世上最難降的妖魔是什么寥茫? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮芭梯,結(jié)果婚禮上弄喘,老公的妹妹穿的比我還像新娘。我一直安慰自己蘑志,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布澎媒。 她就那樣靜靜地躺著波桩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪柏卤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天勾笆,我揣著相機與錄音桥滨,去河邊找鬼。 笑死齐媒,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的邀杏。 我是一名探鬼主播唬血,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼脖律!你這毒婦竟也來了腕侄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤微姊,失蹤者是張志新(化名)和其女友劉穎拌汇,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體噪舀,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年与倡,在試婚紗的時候發(fā)現(xiàn)自己被綠了纺座。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖喳瓣,靈堂內(nèi)的尸體忽然破棺而出赞别,到底是詐尸還是另有隱情,我是刑警寧澤惠毁,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布崎页,位于F島的核電站,受9級特大地震影響飒焦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜丈挟,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一志电、第九天 我趴在偏房一處隱蔽的房頂上張望蛔趴。 院中可真熱鬧,春花似錦鱼蝉、人聲如沸箫荡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至利术,卻和暖如春低矮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工昨悼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留跃洛,地道東北人。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓闲延,卻偏偏與公主長得像韩玩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子找颓,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,691評論 2 361

推薦閱讀更多精彩內(nèi)容