JavaScript Observer (Publish/Subscribe) Pattern

That’s the pattern that I love. It seems to be a core part of JavaScript. And it gives ability of quite many ways of implementing it depending on requirements.

Definition

The Observer Pattern defines a one-to-many dependency between objects so that when one object changes state, all of its dependents are notified and updated automatically.

Taking in account previous patterns this pattern adds one more principle:

  • Strive for loosely coupled designs between objects that interact

So the main idea is that we have one main object to which you subscribe (Subject/Observable) and a lot of objects (Observers) that subscribe and wait for events.

Drawing1

One very important thing to remember is that objects may receive messages in random order, so you shouldn’t rely on the order in which you added Observers.

Basic Observer Example

What will follow is not the simplest example. The simplest example would be the one that is not passing the message.

var Observable = {
    observers: []
  , addObserver: function(observer) {
      this.observers.push(observer)
    }
  , removeObserver: function(observer) {
      var index = this.observers.indexOf(observer)

      if (~index) {
        this.observers.splice(index, 1)
      }
    }
  , notifyObservers: function(message) {
      for (var i = this.observers.length - 1; i >= 0; i--) {
        this.observers[i](message)
      };
    }
  }

Observable.addObserver(function(message){
  console.log("First observer message:" + message)
})

var observer = function(message){
  console.log("Second observer message:" + message)
}

Observable.addObserver(observer)

Observable.notifyObservers('test 1')
// Second observer message:test 1
// First observer message:test 1

Observable.removeObserver(observer)

Observable.notifyObservers('test 2')
// First observer message:test 2

If you want to remove observers using some sort of ID instead of passing callback it can be done in following way:

var Observable = {
    observers: []
  , lastId: -1
  , addObserver: function(observer) {
      this.observers.push({
        callback: observer
      , id: ++this.lastId
      })

      return this.lastId
    }
  , removeObserver: function(id) {
      for (var i = this.observers.length - 1; i >= 0; i--) {
        this.observers[i]
        if (this.observers[i].id == id) {
          this.observers.splice(i, 1)
          return true
        }
      }

      return false
    }
  , notifyObservers: function(message) {
      for (var i = this.observers.length - 1; i >= 0; i--) {
        this.observers[i].callback(message)
      };
    }
  }

var id_1 = Observable.addObserver(function(message){
  console.log("First observer message:" + message)
})

var observer = function(message){
  console.log("Second observer message:" + message)
}

var id_2 = Observable.addObserver(observer)

Observable.notifyObservers('test 1')
Observable.removeObserver(id_2)
Observable.notifyObservers('test 2')

Pull vs Push

There are two main strategies for observer pattern:

  • Push behaviour - when an event happens Observable object will notify all Observers by sending all the new data to them
  • Pull behaviour - when an event happens Observable object will notify all Observers and each Observer will pull the information it needs from the Observable

Pull method is more preferable as in this case you’ll ask only for data that you need. Otherwise at some point you Observable may send huge objects with a lot of attributes. In this example Observable will only notify Observers that something changed and each Observer will take the data it needs. Also in this example we hide observers array and private values in anonymous function closure.

var Observable = {}

;(function(O){
  var observers = []
    , privateVar

  O.addObserver = function(observer) {
    observers.push(observer)
  }

  O.removeObserver = function(observer) {
    var index = observers.indexOf(observer)

    if (~index) {
      observers.splice(index, 1)
    }
  }

  O.notifyObservers = function() {
    for (var i = observers.length - 1; i >= 0; i--) {
      observers[i].update()
    };
  }

  O.updatePrivate = function(newValue) {
    privateVar = newValue
    this.notifyObservers()
  }

  O.getPrivate = function() {
    return privateVar
  }
}(Observable))

Observable.addObserver({
  update: function(){
    this.process()
  }
, process: function(){
    var value = Observable.getPrivate()
    console.log("Private value is: " + value)
  }
})

Observable.updatePrivate('test 1')
// Private value is: test 1

Observable.updatePrivate('test 2')
// Private value is: test 2

Observer with topics

In order not to create multiple observable objects it is much better to add topic functionality to Observer pattern. In simplest form it may look like:

var Observable = {
    observers: []
  , addObserver: function(topic, observer) {
      this.observers[topic] || (this.observers[topic] = [])

      this.observers[topic].push(observer)
    }
  , removeObserver: function(topic, observer) {
      if (!this.observers[topic])
        return;

      var index = this.observers[topic].indexOf(observer)

      if (~index) {
        this.observers[topic].splice(index, 1)
      }
    }
  , notifyObservers: function(topic, message) {
      if (!this.observers[topic])
        return;

      for (var i = this.observers[topic].length - 1; i >= 0; i--) {
        this.observers[topic][i](message)
      };
    }
  }

Observable.addObserver('cart', function(message){
  console.log("First observer message:" + message)
})

Observable.addObserver('notificatons', function(message){
  console.log("Second observer message:" + message)
})

Observable.notifyObservers('cart', 'test 1')
// First observer message:test 1

Observable.notifyObservers('notificatons', 'test 2')
// Second observer message:test 2

More advanced versions may have features like:

  • Subtopics (e.x. /bar/green or bar.green)
  • Publishing to topic propagates to subtopics
  • Publishing to all topics
  • Giving a priority to subscribers

Observer Pattern using jQuery.Callback

jQuery has quite a nice feature like $.Callback. Besides of classical Observer functionality it also has a set of flags:

  • once: Ensures the callback list can only be fired once (like a Deferred)
  • memory: Keeps track of previous values and will call any callback added after the list has been fired right away with the latest "memorized" values (like a Deferred).
  • unique: Ensures a callback can only be added once (so there are no duplicates in the list).
  • stopOnFalse: Interrupts callings when a callback returns false.

Using this options your can customise your Observer in quite interesting ways. Lets see the most simple example using jQuery.Callback:

var callbacks = jQuery.Callbacks()
  , Topic = {
      publish: callbacks.fire,
      subscribe: callbacks.add,
      unsubscribe: callbacks.remove
    }

function fn1( value ){
  console.log( "fn1: " + value );
}

function fn2( value ){
  console.log("fn2: " + value);
}

Topic.subscribe(fn1);
Topic.subscribe(fn2);

Topic.publish('hello world!');
Topic.publish('woo! mail!');

If you want to see a more advanced example with topic than take a look at this [1a] example.

CoffeeScript example

This is a simple example without topics. Almost the same example (at least the same amount of lines) can be found in CoffeeScript Cookbook [7].

class Observable
    constructor: () ->
        @subscribers = []
    subscribe: (callback) ->
        @subscribers.push callback
    unsubscribe: (callback) ->
        @subscribers = @subscribers.filter (item) -> item isnt callback
    notify: () ->
        subscriber() for subscriber in @subscribers

class Observer1
    onUpdate: () ->
        console.log "1st got new message"

class Observer2
    onUpdate: () ->
        console.log "2nd updated"

observable = new Observable()
observer1 = new Observer1()
observer2 = new Observer2()

observable.subscribe observer1.onUpdate
observable.subscribe observer2.onUpdate
observable.notify()

Sources

  1. (github) shichuan / javascript-patterns / design-patterns / observer.html and jQuery examples
  2. (book) Head First Design Patterns
  3. (book) Learning JavaScript Design Patterns
  4. (book) JavaScript Patterns: Build Better Applications with Coding and Design Patterns
  5. (book) Learning JavaScript Design Patterns: A JavaScript and jQuery Developer's Guide
  6. (book) Pro JavaScript Design Patterns: The Essentials of Object-Oriented JavaScript Programming
  7. (book) CoffeeScript Cookbook
  8. (article) dofactory JavaScript Observer Pattern