玩玩JS設計模式之:發佈/訂閱

前言

Node.js的 events 模塊功能強大,除了常規的監聽、觸發,還支持事件順序(prependListener),本文只是寫着玩玩,真正要用的話,仍是選擇成熟穩定的東西較好!html

內容概覽: 如下訂閱=監聽、發佈=觸發;通常來講,先訂閱事件再發布事件;就像打電話同樣,電話沒撥通(訂閱),你就開始說話要幹嗎幹嗎(發佈),這時候訂閱是無效的!!!由於觸發在前、監聽在後,觸發的時候沒有監聽,監聽的時候已經結束,兩者不在一個頻道!!! 溝通就是無效的。。。node

四個功能:設計模式

  • 訂閱on
  • 訂閱once(一次性
  • 發佈emit
  • 註銷off
  • 錯誤監聽error

構造函數

// 發佈訂閱,回調函數版本
function EvtEmit() {
  // 事件參數隊列
  this.evtList = [];
}
複製代碼

原型

EvtEmit.prototype = {
  constructor: EvtEmit,
  // 訂閱事件(監聽)
  on(emitName, handler) {
    // console.debug(`EvtEmit -- on: ${emitName}`);
    if (!emitName) return;
    if (!this.evtList.some(evt => evt.emitName === emitName)) {
      this.evtList.push({
        emitName, // 事件名稱
        handler,
        once: emitName === 'error' ? true : false
      });
    }
  },
  // 訂閱事件(一次性
  once(emitName, handler) {
    if (!emitName) return;
    if (!this.evtList.some(evt => evt.emitName === emitName)) {
      this.evtList.push({
        emitName, // 事件名稱
        handler,
        once: true
      });
    }
  },
  // 發佈事件(觸發)
  emit(emitName, ...param) {
    // console.debug(`EvtEmit -- emit: ${emitName}`);
    if (!emitName) return;
    let evtThis = this.evtList.find(evt => evt.emitName === emitName);
    if (!evtThis) {
      if (emitName !== 'error') {
        console.warn(`請先使用監聽on('${emitName}', callback),再emit('${emitName}')!`);
      }
      return;
    }
    // 一次性訂閱
    if (evtThis.once) this.off(emitName);

    // 監聽[emitName]回調的錯誤
    try {
      evtThis.handler(...param);
    }
    catch (err) {
      // 不使用 on('error', callback)監聽時,打印錯誤
      if (!this.evtList.some(evt => evt.emitName === 'error')) {
        console.error(`on('${emitName}', callback) -> callback Error: ${err}`);
      }

      // 錯誤觸發,可使用 on('error', callback)監聽
      this.emit('error', {
        emitName,
        err
      });
    }
  },
  // 註銷事件訂閱
  off(emitName, callback = null) {
    let arr = this.evtList.filter(evt => evt.emitName !== emitName);
    this.evtList = arr;
    arr = null;
    if (callback) {
      callback.call(this, emitName);
    }
  }
};

複製代碼

使用

同正常的發佈訂閱同樣,先訂閱(on)再發布(emit);api

const evt = new EvtEmit();

// 監聽'run'事件
// 執行1次on監聽,10次回調函數
evt.on('run', res => {
  console.log('res: ', res);
  // 註銷監聽,如下 emit 以後將再也不觸發 on;註釋以後將無限調用
  if (--res < 1) {
    evt.off('run', emitName => {
      console.log(`on('${emitName}')已註銷!`);
    });
    return;
  }

  evt.emit('run', res);
});

evt.emit('run', 10);

複製代碼

輸出

錯誤監聽

try/catch執行監聽的回調函數,捕獲錯誤而後觸發emit('error', err),經過on('error', callback)監聽錯誤;函數

爲何須要 try/catch?
不使用try/catch捕獲錯誤的話,一旦發生錯誤,進程就掛了,這時,後續不須要依賴你此次操做結果的 程序就會跑不下去了!!!(以下 打印 'after go',若是沒有try/catch,那麼他就不會被打印);
這在服務端用的比較多,想象一下,一個接口由於某次調用的參數不合法或者其餘因素,致使程序中斷而影響到後續使用,可能產生‘事故’!ui

try/catch 包裹回調函數的執行

// 發佈事件(觸發)
  emit(emitName, ...param) {
    // console.debug(`EvtEmit -- emit: ${emitName}`);
    if (!emitName) return;
    let evtThis = this.evtList.find(evt => evt.emitName === emitName);
    if (!evtThis) {
      if (emitName !== 'error') {
        console.warn(`請先使用監聽on('${emitName}', callback),再emit('${emitName}')!`);
      }
      return;
    }
    // 一次性訂閱
    if (evtThis.once) this.off(emitName);

    // 監聽[emitName]回調的錯誤
    try {
      evtThis.handler(...param);
    }
    catch (err) {
      // 不使用 on('error', callback)監聽時,打印錯誤
      if (!this.evtList.some(evt => evt.emitName === 'error')) {
        console.error(`on('${emitName}', callback) -> callback Error: ${err}`);
      }

      // 錯誤觸發,可使用 on('error', callback)監聽
      this.emit('error', {
        emitName,
        err
      });
    }
  },
複製代碼

錯誤捕獲:

evt.on('error', ({ emitName, err }) => {
  console.error(`on('${emitName}', callback) -> callback Error: ${err}`);
})

evt.on('go', res => {
  err; // 錯誤會被 try/catch 捕獲
  console.log('res: ', res);
});

evt.emit('go', 'go');
console.log('after go'); // 沒有 try/catch 的話,不會執行
複製代碼

所有代碼 EvtEmit_callback.js:

// EvtEmit_callback.js
// 發佈訂閱,回調函數版本
function EvtEmit() {
  // 事件參數隊列
  this.evtList = [];
}
EvtEmit.prototype = {
  constructor: EvtEmit,
  // 訂閱事件(監聽)
  on(emitName, handler) {
    // console.debug(`EvtEmit -- on: ${emitName}`);
    if (!emitName) return;
    if (!this.evtList.some(evt => evt.emitName === emitName)) {
      this.evtList.push({
        emitName, // 事件名稱
        handler,
        once: emitName === 'error' ? true : false
      });
    }
  },
  // 訂閱事件(一次性
  once(emitName, handler) {
    if (!emitName) return;
    if (!this.evtList.some(evt => evt.emitName === emitName)) {
      this.evtList.push({
        emitName, // 事件名稱
        handler,
        once: true
      });
    }
  },
  // 發佈事件(觸發)
  emit(emitName, ...param) {
    // console.debug(`EvtEmit -- emit: ${emitName}`);
    if (!emitName) return;
    let evtThis = this.evtList.find(evt => evt.emitName === emitName);
    if (!evtThis) {
      if (emitName !== 'error') {
        console.warn(`請先使用監聽on('${emitName}', callback),再emit('${emitName}')!`);
      }
      return;
    }
    // 一次性訂閱
    if (evtThis.once) this.off(emitName);

    // 監聽[emitName]回調的錯誤
    try {
      evtThis.handler(...param);
    }
    catch (err) {
      // 不使用 on('error', callback)監聽時,打印錯誤
      if (!this.evtList.some(evt => evt.emitName === 'error')) {
        console.error(`on('${emitName}', callback) -> callback Error: ${err}`);
      }

      // 錯誤觸發,可使用 on('error', callback)監聽
      this.emit('error', {
        emitName,
        err
      });
    }
  },
  // 註銷事件訂閱
  off(emitName, callback = null) {
    let arr = this.evtList.filter(evt => evt.emitName !== emitName);
    this.evtList = arr;
    arr = null;
    if (callback) {
      callback.call(this, emitName);
    }
  }
};

const evt = new EvtEmit();

// 執行1次on監聽,10次回調函數
evt.on('run', res => {
  console.log('res: ', res);
  // 註銷監聽,如下 emit 以後將再也不觸發 on;註釋以後將無限調用
  if (--res < 1) {
    evt.off('run', emitName => {
      console.log(`on('${emitName}')已註銷!`);
    });
    return;
  }

  evt.emit('run', res);
});

evt.emit('run', 10);


// evt.on('error', ({ emitName, err }) => {
// console.error(`on('${emitName}', callback) -> callback Error: ${err}`);
// })

// evt.on('go', res => {
// err; // 錯誤會被 try/catch 捕獲
// console.log('res: ', res);
// });

// evt.emit('go', 'go');
// console.log('after go'); // 沒有 try/catch 的話,不會執行

複製代碼

參考

  1. js設計模式之發佈/訂閱模式模式
  2. events(事件觸發器)
相關文章
相關標籤/搜索