如何正確使用Node.js事件

翻譯:瘋狂的技術宅
原文: https://medium.freecodecamp.o...

clipboard.png


本文首發微信公衆號:前端先鋒
歡迎關注,天天都給你推送新鮮的前端技術文章javascript


事件驅動的編程變得流行以前,在程序內部進行通訊的標準方法很是簡單:若是一個組件想要向另一個發送消息,只是顯式地調用了那個組件上的方法。可是在 react 中用的倒是事件驅動而不是調用html

事件的好處

這種方法可以使組件更加分離。在咱們繼續寫程序時,會識別整個過程當中的事件,在正確的時間觸發它們,併爲每一個事件附加一個或多個事件監聽器,這使得功能擴展變得更加容易。咱們能夠爲特定事件添加更多的 listener,而沒必要修改現有的偵聽器或觸發事件的應用程序部分。咱們所談論的是觀察者模式。前端

clipboard.png

設計一個事件驅動的體系結構

對事件進行識別很是重要,咱們不但願最終必須從系統中刪除或替換現有事件,由於這可能會迫使咱們刪除或修改附加到事件上的衆多偵聽器。個人通常原則是僅在業務邏輯單元完成執行時才考慮觸發事件。java

假如你想在用戶註冊後發送一堆不一樣的電子郵件。註冊過程自己可能會涉及許多複雜的步驟和查詢,但從商業角度來看,這只是其中的一個步驟。每一個要發送的電子郵件也是單獨的步驟。所以,一旦註冊完成立刻就發佈事件是頗有意義的。因而咱們附加了多個監聽器,每一個監聽器負責發送一種類型的電子郵件。node

Node的異步事件驅動架構具備一些被稱爲「emitters」的對象。它們發出命名事件,這些事件會調用被稱爲「listener」的函數。發出事件的全部對象都是 EventEmitter 類的實例。使用它,咱們能夠建立本身的事件:react

一個例子

讓咱們使用內置的 events 模塊(我建議你查看這個文檔:https://nodejs.org/api/events...)以獲取對 EventEmitter 的訪問權限。程序員

const EventEmitter = require('events');

const myEmitter = new EventEmitter();

module.exports = myEmitter;

這是咱們的服務器端程序的一部分,它負責接收HTTP請求,保存新用戶併發出事件:面試

const myEmitter = require('./my_emitter');

// Perform the registration steps

// Pass the new user object as the message passed through by this event.
myEmitter.emit('user-registered', user);

附加一個監聽器的單獨模塊:編程

const myEmitter = require('./my_emitter');

myEmitter.on('user-registered', (user) => {
  // Send an email or whatever.
});

將策略與實現分開是一種很是好的作法。在這種狀況下,策略意味着哪些 listener 訂閱了哪些事件。實現意味着 listener 本身。segmentfault

const myEmitter = require('./my_emitter');
const sendEmailOnRegistration = require('./send_email_on_registration');
const someOtherListener = require('./some_other_listener');

myEmitter.on('user-registered', sendEmailOnRegistration);
myEmitter.on('user-registered', someOtherListener);
module.exports = (user) => {
  // Send a welcome email or whatever.
}

這種分離使 listener 也能夠被重複使用,它能夠被附加到發送相同消息的其餘事件上(用戶對象)。一樣重要的是 當多個 listener 被附加到單個事件時,它們將按照附加的順序同步執行。所以 someOtherListener 將在 sendEmailOnRegistration 完成執行後運行。

可是,若是你但願本身的 listener 以異步方式運行,只需用 setImmediate 包裝它們的實現,以下所示:

module.exports = (user) => {
  setImmediate(() => {
    // Send a welcome email or whatever.
  });
}

讓你的 Listeners 保持簡潔

在寫 listener 時要堅持單一責任原則。一個 listener 應該只作一件事並把事情作好。例如:要避免在 listener 中編寫太多的條件並根據事件傳來的數據(消息)去決定作什麼。在這種狀況下使用不一樣的事件會更加合適:

const myEmitter = require('./my_emitter');

// Perform the registration steps

// The application should react differently if the new user has been activated instantly.
if (user.activated) {
  myEmitter.emit('user-registered:activated', user);
  
} else {
  myEmitter.emit('user-registered', user);
}
const myEmitter = require('./my_emitter');
const sendEmailOnRegistration = require('./send_email_on_registration');
const someOtherListener = require('./some_other_listener');
const doSomethingEntirelyDifferent = require('./do_something_entirely_different');


myEmitter.on('user-registered', sendEmailOnRegistration);
myEmitter.on('user-registered', someOtherListener);

myEmitter.on('user-registered:activated', doSomethingEntirelyDifferent);
view raw

必要時明確分離 Listener

在前面的例子中,咱們的 listener 是徹底獨立的函數。可是在 listener 與對象關聯的狀況下(這時是一種方法),必須手動將其從已訂閱的事件中分離出來。不然對象將永遠不會被垃圾回收,由於對象( listener )的一部分將會繼續被外部對象( emitter )引用,因此存在內存泄漏的可能。

例如,若是咱們正在開發一個聊天程序,而且但願當新消息到達用戶進入的聊天室時,顯示通知的功能應該位於該用戶對象自己的內部,咱們可能會這樣作:

class ChatUser {
  
  displayNewMessageNotification(newMessage) {
    // Push an alert message or something.
  }
  
  // `chatroom` is an instance of EventEmitter.
  connectToChatroom(chatroom) {
    chatroom.on('message-received', this.displayNewMessageNotification);
  }

  disconnectFromChatroom(chatroom) {
    chatroom.removeListener('message-received', this.displayNewMessageNotification);
  }
}

當用戶關閉他的標籤或暫時斷開互聯網鏈接時,咱們可能但願在服務器端發起一個回調,通知其餘用戶有人剛剛下線。固然在這時爲脫機用戶調用 displayNewMessageNotification 沒有任何意義。除非咱們刪除它,不然它將繼續被用於調用新消息。若是不這樣作,除了沒必要要的調用以外,用戶對象也會被永久地保留在內存中。所以在用戶脫機時應該在服務器端回調中調用 disconnectFromChatroom

注意事項

若是不當心,即使是鬆散耦合的事件驅動架構也會致使複雜性的增長,可能會致使在系統中跟蹤依賴關係變得很困難。若是咱們從偵聽器內部發出事件,程序會特別容易出現這類問題。這可能會觸發意外的事件鏈。


本文首發微信公衆號:前端先鋒

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章


歡迎繼續閱讀本專欄其它高贊文章:

相關文章
相關標籤/搜索