Javascript 異步編程(四)發布-訂閱

理解發布/訂閱

我們先來看一段用來模擬異步請求嵌套的代碼

let ajax = function (url, cb) {
  // 省略邏輯
}
ajax('/queryOne', function (data1) {
  console.log(data1)
  ajax('/queryTwo' + data1.someProp, function (data2) {
    console.log(data2)
    ajax('/queryThree' + data2.someProp, function (data3) {
      console.log(data3)
    })
  })
})

以上三次異步請求中:

  • 內層依賴于外層,高耦合
  • 不符合閱讀習慣
  • 不利于調試以及排查定位問題

舉個例子,現實中大家都會微信關注公眾號,會不定期的收到推送。這個就好比我們訂閱了某一類主題,產生消息后,統一推送給所有關注了該公眾號的人。

發布/訂閱模式,屬于設計模式中的行為模式

image_11.png

其優點:

  1. 解耦
  2. 可伸縮性
  3. 高可靠性
  4. 可測試性

缺點:

  • 無法確保消息被觸發或者觸發了幾次

是Promise方案之前的主流方案

實現發布/訂閱

有三個要素:

  • 事件發布:觸發event上綁定的函數
  • 事件監聽:注冊函數到event上
  • 事件池,存放 event 以及預定的函數
class EventCenter {
  constructor () {
    this.events = {}
  }

  // 發布事件 觸發對應消息的處理函數
  publish (eventName, data) {
    if (this.events[eventName]) {
      // 一個消息會有多個訂閱者使用數組存放
      this.events[eventName].forEach(callback => {
        callback.apply(this, data)
      })
    }
  }

  // 訂閱事件 綁定函數與消息
  subscribe (eventName, callback) {
    if (this.events[eventName]) {
      this.events[eventName].push(callback)
    } else {
      this.events[eventName] = [callback]
    }
  }

  // 取消訂閱
  unSubscribe (eventName, callback) {
    if (!this.events.hasOwnProperty(eventName)) return false
    // 清除該消息的所有訂閱者
    if (!callback) this.events[eventName] = []
    // 清除該消息的特定訂閱者
    this.events[eventName] = this.events[eventName].filter(
      cb => cb !== callback)
  }
}

Node.js中的發布/訂閱

在Node.js中,方法默認是異步,同步方法一般會以xxxSync進行區別。

const fs = require('fs');

fs.readFile(__diraname,function(err,data) {
  if(err)  console.log(error);
  console.log('end async read'); 
})

let data=fs.readFileSync(__diraname);
console.log('end sync read')

EventEmitter 核心就是事件觸發與事件監聽器功能的封裝,在Node.js中主要用于處理異步I/O操作,這個類在Node內置模塊以及第三方模塊中

const { EventEmitter } = require('events')
const event = new EventEmitter()

event.on('some_event', function (data) {
  console.log('some_event 被觸發',data)
})

setTimeout(function () {
  event.emit('some_event','trigger by setTimeout')
}, 3000)

// some_event 被觸發 trigger by setTimeout

常用方法:

  • addListener(event, listener):為指定事件添加一個監聽器到監聽器數組的尾部
  • prependListener(event,listener):為指定事件添加一個監聽器到監聽器數組頭部
  • on(event, listener) :是addListener的別名。
  • once(event, listener):為指定事件注冊一個單次監聽器,即 監聽器最多只會觸發一次,觸發后立刻解除該監聽器。
  • removeListener(event, listener):移除指定事件的某個監聽器,監聽器必須是該事件已經注冊過的監聽器。
  • off(event,listener):是removeListener的別名。
  • removeAllListeners([event]):移除所有事件的所有監聽器, 如果指定事件,則移除指定事件的所有監聽器。
  • listeners(event):返回指定事件的監聽器數組。
  • emit(event, [arg1], [arg2], [...]):按監聽器的順序執行執行每個監聽器,如果事件有注冊監聽返回 true,否則返回 false。

當添加新的監聽器時,'newListener' 事件會觸發,當監聽器被移除時,'removeListener' 事件被觸發。

關于error事件

EventEmitter 定義了一個特殊的事件 error,它包含了錯誤的語義,我們在遇到 異常的時候通常會觸發 error 事件。

當 error 被觸發時,EventEmitter 規定如果沒有響 應的監聽器,Node.js 會把它當作異常,退出程序并輸出錯誤信息。

我們一般要為會觸發 error 事件的對象設置監聽器,避免遇到錯誤后整個程序崩潰。例如:

const {EventEmitter} = require('events'); 
const  emitter = new EventEmitter(); 
emitter.on('error',function(err) {
  console.log("出現異常")
})
emitter.emit('error'); 

框架中的發布/訂閱

jQuery 的 on 和 trigger

/* 事件訂閱*/
$('#app').on('myevent', eventHandler)
// 發布
$('#app').trigger('myevent')
/* 取消訂閱*/
$('#app').off('myevent')

Vue 的雙向數據綁定

Vue 的父子組件通信 on/emit

觀察者模式?

觀察者模式

觀察者模式是軟件設計模式的一種。在此種模式中,一個目標對象管理所有相依于它的觀察者對象,并且在它本身的狀態改變時主動發出通知。這通常透過呼叫各觀察者所提供的方法來實現。此種模式通常被用來實時事件處理系統。 —— 維基百科

class Subject {
  constructor () {
    this.subscribes = []
  }

  on (subscriber) {
    this.subscribes.push(subscriber)
  }

  off (subscriber) {
    this.subscribes = this.subscribes.filter(sub => sub !== subscriber)
  }
  notify(){
    this.subscribes.forEach(sub=>{
      sub.update()
    })
  }
}
class Observer {
  update(){
    console.log('update...')
  }
}

let subject=new Subject();
let ob1=new Observer();
let ob2=new Observer();
subject.on(ob1)
subject.on(ob2)
subject.notify()

訂閱/發布模式

在軟件架構中,發布-訂閱是一種消息范式,消息的發送者(稱為發布者)不會將消息直接發送給特定的接收者(稱為訂閱者)。而是將發布的消息分為不同的類別,無需了解哪些訂閱者(如果有的話)可能存在。同樣的,訂閱者可以表達對一個或多個類別的興趣,只接收感興趣的消息,無需了解哪些發布者(如果有的話)存在。—— 維基百科

區別

image_13.png
  1. 訂閱/發布模式,使用event-center這個中介角色來發布消息和訂閱消息,而 觀察者模式是由觀察者直接通知該消息的訂閱者(沒有中間商賺差價)
  2. 在發布者/訂閱者模式中,組件與觀察者模式完全分離。在觀察者模式中,主題和觀察者松散耦合。

總結

  1. 一般流程是,先訂閱事件,再觸發事件(但可自行實現支持先觸發 緩存后,再訂閱)
  2. 訂閱/發布模式 與 觀察者模式的最大區別就是 引入了 event-center
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,699評論 2 370

推薦閱讀更多精彩內容