Skip to content

Instantly share code, notes, and snippets.

@danybeltran
Last active February 1, 2022 17:42
Show Gist options
  • Save danybeltran/4d8661bc67635406e67ccb06c0878695 to your computer and use it in GitHub Desktop.
Save danybeltran/4d8661bc67635406e67ccb06c0878695 to your computer and use it in GitHub Desktop.
Observer pattern in javascript
const { randomUUID } = require("crypto")
/**
* Create an observable value
*/
class Observable {
observableValue
subscribers = {}
/**
* Get current value
*/
get value() {
return this.observableValue
}
/**
* Add a listener to changes in observableValue
*/
addListener(listener) {
const listenerId = randomUUID()
this.subscribers[listenerId] = listener
return listenerId
}
/**
* Remove a subscriber by passing its refference
*/
removeListener(listener) {
switch (typeof listener) {
case "function":
for (let listenerId in this.subscribers) {
if (this.subscribers[listenerId] === listener) {
delete this.subscribers[listenerId]
}
}
break
case "string":
delete this.subscribers[listener]
break
default:
break
}
}
notifySubscribers(id = undefined) {
/*
* If specifying a subscriber id
*/
if (id) {
switch (typeof id) {
case "function":
for (let subscriberId in this.subscribers) {
if (this.subscribers[subscriberId] === id) {
this.subscribers[subscriberId](this.value)
}
}
break
case "string":
if (typeof this.subscribers[id] === "function") {
this.subscribers[id](this.value)
}
break
default:
break
}
} else {
/**
* Otherwise notify all subscribers
*/
for (let subscriberId in this.subscribers) {
this.subscribers[subscriberId](this.value)
}
}
}
/**
* Change observable value
*/
set(newValue, subscribersToNotify) {
let $this = this
this.observableValue = newValue(this.value)
/**
* If provided with a list of subscribers
*/
if (Array.isArray(subscribersToNotify)) {
subscribersToNotify.forEach(function (subscriber) {
for (let subscriberId in $this.subscribers) {
if ($this.subscribers[subscriberId] === subscriber) {
$this.notifySubscribers(subscriberId)
}
}
})
} else if ("function" === typeof subscribersToNotify) {
/**
* Notify by one subscriber reference
*/
if ("function" === typeof subscribersToNotify) {
for (let subscriberId in $this.subscribers) {
if ($this.subscribers[subscriberId] === subscriberToNotify) {
$this.notifySubscribers(subscriberId)
}
}
}
} else {
/**
* Otherwise, notify all subscribers
*/
this.notifySubscribers()
}
}
constructor(value) {
this.observableValue = value
}
}
@danybeltran
Copy link
Author

Example:

function main() {
  const Counter = new Observable(0)

  // Will get added when Counter value is 4
  const allListenersRemoved = () => {
    console.log("No active listeners.")
  }

  const countListener = (count) => {
    // Remove listener
    if (count === 4) {
      console.log(`Count has changed to 4. Removing listener.`)
      Counter.removeListener(countListener)
      Counter.addListener(allListenersRemoved)
    } else console.log(`Count has changed to ${count}.`)
  }

  Counter.addListener(countListener)

  // Update value every 2 seconds
  setInterval(() => {
    Counter.set((c) => c + 1)
  }, 2000)
}
main()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment