Skip to content

loilo/monomitter

master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
 
 
 
 
 
 
 
 
 
 
 
 


monomitter logo showing a radio station symbol

monomitter


Tests Version on npm

The monomitter is a tiny (125 bytes minzipped), generic notification helper β€” a kind of topic-free pub/sub mechanism or a single-event event bus β€” designed to be used as a building block for reactive functionality..

Basic Usage

import monomitter from 'monomitter'

// create a monomitter pub/sub pair
const [publish, subscribe] = monomitter()

// log payload on publish
subscribe((...payload) => {
  console.log(payload)
})

publish(1, 2, 3) // logs [1, 2, 3]
publish('Hello world!') // logs ["Hello world!"]

Unsubscribe

The subscribe function returns a callback to be used for unsubscribing:

import monomitter from 'monomitter'

const [publish, subscribe] = monomitter()

// subscribe and get unsubscribe callback
const stop = subscribe((...payload) => {
  console.log(payload)
})

publish(1, 2, 3) // logs [1, 2, 3]

// unsubscribe
stop()

publish(42) // does not log

Clear All Subscribers

The monomitter function returns a third item, a "clear-all" callback:

import monomitter from 'monomitter'

const [publish, subscribe, clear] = monomitter()

// subscribe and get unsubscribe callback
subscribe(() => {
  console.log('hi from subscriber 1')
})
subscribe(() => {
  console.log('hi from subscriber 2')
})

publish(1, 2, 3) // logs "hi from subscriber 1" and "hi from subscriber 2"

// clear all subscribers
clear()

publish(42) // does not log

Examples

monomitter is suitable for a variety of notification-related tasks. Here are some examples:

Observable

Create a watchable value wrapper:

import monomitter from 'monomitter'

// Implementation

function observable(inititalValue) {
  let currentValue = inititalValue

  const [notify, watch] = monomitter()

  return {
    watch,
    get: () => currentValue,
    set: newValue => {
      if (newValue !== currentValue) {
        const oldValue = currentValue
        currentValue = newValue
        notify(newValue, oldValue)
      }
    }
  }
}

// Usage

const value = observable(5)
value.watch((newValue, oldValue) => {
  console.log('Changed from %o to %o', oldValue, newValue)
})
value.get() // returns 5
value.set(10) // logs "Changed from 5 to 10"
value.get() // returns 10

Signaling

Create a controller with a signal (not unlike the AbortController):

import monomitter from 'monomitter'

// Implementation

function Signal(subscribe) {
  this.addListener = subscribe
}

function SignalController() {
  const [publish, subscribe] = monomitter()
  this.trigger = publish
  this.signal = new Signal(subscribe)
}

// Usage

const controller = new SignalController()

// Pass the controller.signal to a consumer who may be interested
controller.signal.addListener(() => {
  console.log('Got a signal!')
})

// Trigger the signal
controller.trigger() // logs "Got a signal!"

Event Emitter

It's even possible to build a fully-fledged event emitter with this. (But why would you if there's mitt β€” this example is very much just a proof of concept.)

import monomitter from 'monomitter'

// Implementation

class EventEmitter {
  constructor() {
    this.eventData = new Map()
  }

  getEventData(event) {
    if (!this.eventData.has(event)) {
      const [emit, listen] = monomitter()
      this.eventData.set(event, {
        emit,
        listen,
        unsubscribers: new WeakMap()
      })
    }

    return this.eventData.get(event)
  }

  on(event, callback) {
    const eventData = this.getEventData(event)
    const unsubscriber = eventData.listen(callback)
    eventData.unsubscribers.set(callback, unsubscriber)
  }

  once(event, callback) {
    const eventData = this.getEventData(event)
    const unsubscriber = eventData.listen((...args) => {
      callback(...args)
      unsubscriber()
    })
    eventData.unsubscribers.set(callback, unsubscriber)
  }

  off(event, callback) {
    this.getEventData(event).unsubscribers.get(callback)?.()
  }

  emit(event, ...payload) {
    this.getEventData(event).emit(...payload)
  }
}

// Usage

const ee = new EventEmitter()

ee.on('load', function onload() {
  console.log('load!')
})
ee.once('load', function onloadOnce() {
  console.log('load once!')
})

ee.emit('load') // logs "load!" and "load once!"
ee.emit('load') // logs "load!"

ee.off('load', onload)

ee.emit('load') // does not log