Library

Observer Pattern

Subscribe a listener to an event and call it on emit by priority. once runs a single time.

JAVASCRIPT Free to use
Code
class EventEmitter {
    constructor() {
        this.events = new Map();
    }

    on(event, listener, priority = 0) {
        return this.add(event, listener, priority, false);
    }

    once(event, listener, priority = 0) {
        return this.add(event, listener, priority, true);
    }

    off(event, listener) {
        const list = this.events.get(event);
        if (!list) {
            return this;
        }

        if (listener == null) {
            this.events.delete(event);
            return this;
        }

        const next = list.filter((entry) => entry.listener !== listener);
        if (next.length) {
            this.events.set(event, next);
        } else {
            this.events.delete(event);
        }

        return this;
    }

    emit(event, ...args) {
        const list = this.events.get(event);
        if (!list || list.length === 0) {
            return false;
        }

        const snapshot = list.slice();

        for (const entry of snapshot) {
            if (entry.once) {
                this.drop(event, entry);
            }
            entry.listener(...args);
        }

        return true;
    }

    listenerCount(event) {
        return this.events.get(event)?.length ?? 0;
    }

    add(event, listener, priority, once) {
        if (typeof listener !== 'function') {
            throw new TypeError('listener must be a function.');
        }

        const list = this.events.get(event) ?? [];
        const entry = { listener, priority, once };
        const index = list.findIndex((item) => item.priority < priority);

        if (index === -1) {
            list.push(entry);
        } else {
            list.splice(index, 0, entry);
        }

        this.events.set(event, list);
        return this;
    }

    drop(event, entry) {
        const list = this.events.get(event);
        if (!list) {
            return;
        }

        const next = list.filter((item) => item !== entry);
        if (next.length) {
            this.events.set(event, next);
        } else {
            this.events.delete(event);
        }
    }
}
Quick try
const bus = new EventEmitter();

const profile = (user) => console.log('profile', user.id);
bus.on('user.created', profile, 1);

bus.once('user.created', (user) => {
    console.log('welcome', user.id);
}, 10);

bus.emit('user.created', { id: 7 });
bus.emit('user.created', { id: 7 }); // once does not run again

bus.off('user.created', profile);

What it does

  • Listeners are copied before emit, so on or off during this turn does not change it.
  • once is removed before it runs, so it cannot fire again if it emits the same event.
  • Errors are not swallowed. A once entry is dropped on its own, even if the same function is also registered with on.

Where it fits

A practical place for this snippet in a real project.

Useful for notifying separate parts of a page after a user is created. It is not a message bus between servers.

Similar snippets

Binary Search Tree
JAVASCRIPT

Binary Search Tree

Insert, search, and delete while keeping values sorted. Average O(log n), worst O(n) if the tree becomes a chain.

Function Memoization
JAVASCRIPT

Function Memoization

Wrap a function and reuse its result for the same inputs. The next call with the same values skips the work.

LRU Cache
JAVASCRIPT

LRU Cache

Keep a fixed number of entries and evict the least recently used item when the cache is full.

Need a snippet that is not here?

Describe the problem or idea, and I will see if it belongs in the library.