Skip to content

Commit

Permalink
📺 run prettier
Browse files Browse the repository at this point in the history
  • Loading branch information
Robin Pokorny committed Jul 7, 2019
1 parent ea9fef2 commit fc7e797
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 155 deletions.
79 changes: 47 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
# :skull::bulb: Dead simple PubSub and EventEmitter

> Small, readable, almost-tweetable modules utilising classic patterns in modern language.
[![Build Status](https://img.shields.io/badge/build-passed-brightgreen.svg?style=flat-square)](https://semaphoreci.com/robinpokorny/dead-simple)
[![license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://github.com/robinpokorny/dead-simple/blob/master/LICENSE)
[![git3moji](https://img.shields.io/badge/git3moji-%E2%9A%A1%EF%B8%8F%F0%9F%90%9B%F0%9F%93%BA%F0%9F%91%AE%F0%9F%94%A4-fffad8.svg?style=flat-square)](https://robinpokorny.github.io/git3moji/)
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-lightgrey.svg?style=flat-square)](http://standardjs.com/)
[![Managed by Yarn](https://img.shields.io/badge/managed%20by-Yarn-2C8EBB.svg?style=flat-square)](https://yarnpkg.com/)

## Features
* **Small:** PubSub: 127 bytes, EventEmitter: 138 bytes, **together: 191 bytes** (all gzipped)
* **Best practices:** Subscribe returns unsubscribe. So you can use anonymous functions.
* **Readable:** There is nothing magical in the code; except the simplicity.
* **Modern:** Uses new, yet [well-supported](#browser-compatibility) features.
* **Efficient:** No memory leaks, no duplicate calls, no extra looping.
* **Clean API:** `sub` and `pub` (or `on` and `emit`), need no more, can't go less.
* **Modular:** Use only what you need.

- **Small:** PubSub: 127 bytes, EventEmitter: 138 bytes, **together: 191 bytes** (all gzipped)
- **Best practices:** Subscribe returns unsubscribe. So you can use anonymous functions.
- **Readable:** There is nothing magical in the code; except the simplicity.
- **Modern:** Uses new, yet [well-supported](#browser-compatibility) features.
- **Efficient:** No memory leaks, no duplicate calls, no extra looping.
- **Clean API:** `sub` and `pub` (or `on` and `emit`), need no more, can't go less.
- **Modular:** Use only what you need.

## Install

Expand All @@ -29,42 +30,43 @@ npm install --save dead-simple
## Usage

```js
import pubsub from 'dead-simple/pubsub'
import eventEmitter from 'dead-simple/eventEmitter'
import pubsub from "dead-simple/pubsub";
import eventEmitter from "dead-simple/eventEmitter";
// Alternatively:
// import { pubsub, eventEmitter } from 'dead-simple'

// === PubSub ======
const clicks = pubsub()
const clicks = pubsub();

const unSub = clicks.sub((target) => console.log(`Clicked on ${target}!`))
const unSub = clicks.sub(target => console.log(`Clicked on ${target}!`));

clicks.pub('button')
clicks.pub("button");
// -> Clicked on button!

unSub()
unSub();

clicks.pub('link')
clicks.pub("link");
// nothing


// === eventEmitter ===
// eventEmitter = named PubSub
const events = eventEmitter()
const events = eventEmitter();

events.on('click', (target) => console.log(`Clicked on ${target}!`))
events.on("click", target => console.log(`Clicked on ${target}!`));

const unSubChange = events.on('change', (newValue) => console.log(`Value is now ${newValue}!`))
const unSubChange = events.on("change", newValue =>
console.log(`Value is now ${newValue}!`)
);

events.emit('change', 1968)
events.emit("change", 1968);
// -> Value is now 1968!

unSubChange()
unSubChange();

events.emit('change', 1968)
events.emit("change", 1968);
// nothing

events.emit('click', 'button')
events.emit("click", "button");
// -> Clicked on button!
```

Expand All @@ -78,26 +80,39 @@ Used ES6: [const](http://kangax.github.io/compat-table/es6/#test-const),
[Set](http://kangax.github.io/compat-table/es6/#test-Set),
[object shorthand](http://kangax.github.io/compat-table/es6/#test-object_literal_extensions_shorthand_properties)

Chrome\* | Edge | FF | IE | Opera | Safari | iOS | Node
---------|------|----|-----|-------|--------|-----|-----
38 | 12 | 13 | -\* | 25 | 7.1 | 8 | 4
| Chrome\* | Edge | FF | IE | Opera | Safari | iOS | Node |
| -------- | ---- | --- | --- | ----- | ------ | --- | ---- |
| 38 | 12 | 13 | -\* | 25 | 7.1 | 8 | 4 |

_Notes:_

*Notes:*
* Chrome includes mobile Chrome (Android 4+).
* IE 11 does *not* support only syntax feature, arrow functions and object shorthand.
* The module needs to be bundled, of course.
- Chrome includes mobile Chrome (Android 4+).
- IE 11 does _not_ support only syntax feature, arrow functions and object shorthand.
- The module needs to be bundled, of course.

### Super small versions

This project started a [pair](https://gist.github.com/robinpokorny/d743ed9e0bc5214f79076a16c8e44a8f) of [gists](https://gist.github.com/robinpokorny/dd97bd013dc5198a5bd0556c591f661c) which included a hand minified version, too.

We were able to get down to **91B** for PubSub:

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

And **139B** for EventEmitter (`p` is PubSub):

```js
export default e=>(e=new Map, Object.freeze({on:(n,f)=>(e.has(name)||e.set(n,p()),e.get(n).sub(f)),emit:(n,d)=>e.has(n)&&e.get(n).pub(d)}))
export default e => (
(e = new Map()),
Object.freeze({
on: (n, f) => (e.has(name) || e.set(n, p()), e.get(n).sub(f)),
emit: (n, d) => e.has(n) && e.get(n).pub(d)
})
);
```

These versions are for fun, more like a proof of concept and may not work in some browsers.
17 changes: 8 additions & 9 deletions eventEmitter.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
const pubsub = require('./pubsub')
const pubsub = require("./pubsub");

module.exports = () => {
const events = new Map()
const events = new Map();

const on = (name, fn) => {
if (!events.has(name)) events.set(name, pubsub())
if (!events.has(name)) events.set(name, pubsub());

return events.get(name).sub(fn)
}
return events.get(name).sub(fn);
};

const emit = (name, data) =>
events.has(name) && events.get(name).pub(data)
const emit = (name, data) => events.has(name) && events.get(name).pub(data);

return Object.freeze({ on, emit })
}
return Object.freeze({ on, emit });
};
118 changes: 60 additions & 58 deletions eventEmitter.spec.js
Original file line number Diff line number Diff line change
@@ -1,62 +1,64 @@
/* eslint-env jest */
const eventEmitter = require('./eventEmitter')
const eventEmitter = require("./eventEmitter");

describe('Event Emitter', () => {
let emitter
const callback1 = jest.fn()
const callback2 = jest.fn()
describe("Event Emitter", () => {
let emitter;
const callback1 = jest.fn();
const callback2 = jest.fn();

beforeEach(() => {
emitter = eventEmitter()
callback1.mockClear()
callback2.mockClear()
})

it('has simple API', () => {
const keys = Object.keys(emitter).sort().join()
expect(keys).toBe('emit,on')
})

it('passes data to registered callbacks', () => {
emitter.on('event', callback1)
emitter.on('click', callback2)

emitter.emit('event', 'foo')
emitter.emit('click', 'bar')

expect(callback1).toHaveBeenCalledTimes(1)
expect(callback1).toHaveBeenCalledWith('foo')
expect(callback2).toHaveBeenCalledTimes(1)
expect(callback2).toHaveBeenCalledWith('bar')
})

it('removes callback when it is unsubscibed', () => {
const unSub = emitter.on('event', callback1)
unSub()
emitter.emit('event', 'foo')

expect(callback1).not.toHaveBeenCalled()
})

it('does not call a callback twice', () => {
emitter.on('event', callback1)
emitter.on('event', callback1)
emitter.emit('event', 'foo')

expect(callback1).toHaveBeenCalledTimes(1)
expect(callback1).toHaveBeenCalledWith('foo')
})

it('does not leak on sucessfull emit', () => {
emitter.on('event', callback1)
const result = emitter.emit('event', 'foo')

expect(result).toBeUndefined()
})

it('returns false on empty emit', () => {
const result = emitter.emit('event', 'foo')

expect(result).toBe(false)
})
})
emitter = eventEmitter();
callback1.mockClear();
callback2.mockClear();
});

it("has simple API", () => {
const keys = Object.keys(emitter)
.sort()
.join();
expect(keys).toBe("emit,on");
});

it("passes data to registered callbacks", () => {
emitter.on("event", callback1);
emitter.on("click", callback2);

emitter.emit("event", "foo");
emitter.emit("click", "bar");

expect(callback1).toHaveBeenCalledTimes(1);
expect(callback1).toHaveBeenCalledWith("foo");
expect(callback2).toHaveBeenCalledTimes(1);
expect(callback2).toHaveBeenCalledWith("bar");
});

it("removes callback when it is unsubscibed", () => {
const unSub = emitter.on("event", callback1);
unSub();
emitter.emit("event", "foo");

expect(callback1).not.toHaveBeenCalled();
});

it("does not call a callback twice", () => {
emitter.on("event", callback1);
emitter.on("event", callback1);
emitter.emit("event", "foo");

expect(callback1).toHaveBeenCalledTimes(1);
expect(callback1).toHaveBeenCalledWith("foo");
});

it("does not leak on sucessfull emit", () => {
emitter.on("event", callback1);
const result = emitter.emit("event", "foo");

expect(result).toBeUndefined();
});

it("returns false on empty emit", () => {
const result = emitter.emit("event", "foo");

expect(result).toBe(false);
});
});
6 changes: 3 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const pubsub = require('./pubsub')
const eventEmitter = require('./eventEmitter')
const pubsub = require("./pubsub");
const eventEmitter = require("./eventEmitter");

module.exports = { pubsub, eventEmitter }
module.exports = { pubsub, eventEmitter };
16 changes: 8 additions & 8 deletions pubsub.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
module.exports = () => {
const subscribers = new Set()
const subscribers = new Set();

const sub = (fn) => {
subscribers.add(fn)
const sub = fn => {
subscribers.add(fn);

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

const pub = (data) => subscribers.forEach((fn) => fn(data))
const pub = data => subscribers.forEach(fn => fn(data));

return Object.freeze({ pub, sub })
}
return Object.freeze({ pub, sub });
};
Loading

0 comments on commit fc7e797

Please sign in to comment.