Skip to content

Instantly share code, notes, and snippets.

@robinpokorny
Last active September 4, 2022 17:27
Show Gist options
  • Save robinpokorny/d743ed9e0bc5214f79076a16c8e44a8f to your computer and use it in GitHub Desktop.
Save robinpokorny/d743ed9e0bc5214f79076a16c8e44a8f to your computer and use it in GitHub Desktop.
📰 Dead simple tweetable JavaScript PubSub pattern module using Set (ES2015)
export default () => {
const subscribers = new Set()
const sub = (fn) => {
subscribers.add(fn)
return () => subscribers.delete(fn)
}
const pub = (data) => subscribers.forEach((fn) => fn(data))
return Object.freeze({ pub, sub })
}
// Minified (by hand), 105 bytes
export default s=>(s=new Set,Object.freeze({pub:d=>s.forEach(f=>f(d)),sub:f=>s.add(f).delete.bind(s,f)}))
import pubsub from './pubsub'
// Create a new PubSub
const events = pubsub()
// Register first subscriber
// Save the unsubscribe callback
const unSubOne = events.sub((a) => console.log('one: ' + a))
const two = (a) => console.log('two: ' + a)
// Register second subscriber
const unSubTwo = events.sub(two)
// Subscriber can be registred only once, the following has no effect
events.sub(two)
// Dispatch a string
// `two` is called just once
events.pub('foo')
// "one: foo"
// "two: foo"
// Deregister a subscriber
// Returns true if the function has been removed successfully
unSubOne() // true
// Dispatch a string
// `one` is not called
events.pub('bar')
// "two: bar"
@robinpokorny
Copy link
Author

This gist's motivation was to create the simplest full-feature PubSub service.

One might want to check that argument of on is a function to prevent a future type error.

Some inspiration: https://gist.github.com/learncodeacademy/777349747d8382bfb722

@robinpokorny
Copy link
Author

robinpokorny commented Aug 12, 2016

As suggested by @rarous (https://twitter.com/alesroubicek/status/764068304080089089) the on can return an unsub callback and remove the off method:

const on = (fn) => {
  subscribers.add(fn)

  return () => subscribers.delete(fn)
}

@robinpokorny
Copy link
Author

OK, so I liked that idea that much I made it the default. See the original version: https://gist.github.com/robinpokorny/d743ed9e0bc5214f79076a16c8e44a8f/f187d1d45873b1687e0a6d1f754a7aa073736742

@robinpokorny
Copy link
Author

I changed the API so the functions exposed are named sub and pub.
Also, because of the API changes it is not 5-line anymore 😞

@robinpokorny
Copy link
Author

robinpokorny commented Aug 26, 2016

I added a minified/uglified version which fits in a tweet!

Also note that module.exports= is the exact same length as export default (16 characters). So you can easily use it in node.js (>=v0.12) without transpiling.

What I used / found out:

  • Moved functions definitions inside Object.freeze.
  • {s.add(f);return _=>s.delete(f)} to s.add(f).delete.bind(s,f). As Set.prototype.add() returns the Set object we can then access the Set.prototype.delete().
  • _=> is one character less than ()=> and works the same! Of course, any letter would do, too.
  • const is 5 letters, var and let just 3. If it works with const it will work with var and let.

@robinpokorny
Copy link
Author

robinpokorny commented Aug 26, 2016

The following has only 104 bytes, but allows a param during init:

export default (s=new Set)=>Object.freeze({pub:d=>s.forEach(f=>f(d)),sub:f=>s.add(f).delete.bind(s,f)})

I decided not to use this one as it is potentially dangerous.

@robinpokorny
Copy link
Author

Oh, the very minimal version would be 91 characters:

export default (s=new Set)=>({pub:d=>s.forEach(f=>f(d)),sub:f=>s.add(f).delete.bind(s,f)})

That is removing Object.freeze. I would not recommend using that one though…

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