Skip to main content

Pub-Sub

  • 发布-订阅模式是借助第三方来实现调度, 发布者和订阅者之间互不感知.
  • 观察者模式中主体和观察者是互相感知.
  • 符合开放封闭原则.
  • 存在依赖追踪困难可能, 导致调试困难.
Pub-Sub Use Case
  • Event listener and handler.
  • Event Bus in Vue.
  • Event Emitter in Node.
  • 异步编程.
class PubSub {
constructor() {
// Broadcast channel
this.topics = {}
// Topic identifier
this.subUid = -1
}

publish(topic, args) {
if (!this.topics[topic])
return false

const subscribers = this.topics[topic]
let len = subscribers ? subscribers.length : 0

while (len--) subscribers[len].func(topic, args)

return this
}

subscribe(topic, func) {
if (!this.topics[topic])
this.topics[topic] = []

const token = (++this.subUid).toString()
this.topics[topic].push({
token,
func,
})
return token
}

unsubscribe(token) {
for (const m in this.topics) {
if (this.topics[m]) {
for (let i = 0, j = this.topics[m].length; i < j; i++) {
if (this.topics[m][i].token === token) {
this.topics[m].splice(i, 1)
return token
}
}
}
}

return this
}
}

const pubsub = new PubSub()
const token = pubsub.subscribe('/addFavorite', (topic, args) => {
console.log('test', topic, args)
})
pubsub.publish('/addFavorite', ['test'])
pubsub.unsubscribe(token)

jQuery event system:

// Equivalent to subscribe(topicName, callback)
$(document).on('topicName', () => {
// ..perform some behavior
})

// Equivalent to publish(topicName)
$(document).trigger('topicName')

// Equivalent to unsubscribe(topicName)
$(document).off('topicName')

Event emitter:

class MicroEvent {
bind(event, callback) {
this._events = this._events || {}
this._events[event] = this._events[event] || []
this._events[event].push(callback)
}

unbind(event, callback) {
this._events = this._events || {}

if (event in this._events === false)
return

this._events[event].splice(this._events[event].indexOf(callback), 1)
}

trigger(event, ...args) {
this._events = this._events || {}

if (event in this._events === false)
return

for (let i = 0; i < this._events[event].length; i++)
this._events[event][i].apply(this, args)
}
}

AJAX callback:

  • 当请求返回, 并且实际的数据可用的时候, 会生成一个通知.
  • 如何使用这些事件(或者返回的数据), 都是由订阅者自己决定的.
  • 可以有多个不同的订阅者, 以不同的方式使用返回的数据.
  • AJAX 层: 唯一的责任 - 请求和返回数据, 接着将数据发送给所有想要使用数据的地方.
;(function ($) {
// Pre-compile template and "cache" it using closure
const resultTemplate = _.template($('#resultTemplate').html())

// Subscribe to the new search tags topic
$.subscribe('/search/tags', (tags) => {
$('#searchResults').html(`Searched for: ${tags}`)
})

// Subscribe to the new results topic
$.subscribe('/search/resultSet', (results) => {
$('#searchResults').append(resultTemplate(results))
})

// Submit a search query and publish tags on the /search/tags topic
$('#flickrSearch').submit(function (e) {
e.preventDefault()
const tags = $(this).find('#query').val()

if (!tags)
return

$.publish('/search/tags', [$.trim(tags)])
})

// Subscribe to new tags being published and perform
// a search query using them. Once data has returned
// publish this data for the rest of the application
// to consume

$.subscribe('/search/tags', (tags) => {
// Ajax Request
$.getJSON(
'http://api.flickr.com/services/feeds/',
{
tags,
tagMode: 'any',
format: 'json',
},

(data) => {
if (!data.items.length)
return

$.publish('/search/resultSet', data.items)
}
)
})
})()