84

It is so easy to use eventEmitter in node.js:

var e = new EventEmitter();
e.on('happy', function(){console.log('good')});
e.emit('happy');

Any client side EventEmitter in browser native?

Xin
  • 33,823
  • 14
  • 84
  • 85

12 Answers12

132

In modern browsers, there is EventTarget.

class MyClass extends EventTarget {
  doSomething() {
    this.dispatchEvent(new Event('something'));
  }
}

const instance = new MyClass();
instance.addEventListener('something', (e) => {
  console.log('Instance fired "something".', e);
});
instance.doSomething();

Additional Resources:

Brad
  • 159,648
  • 54
  • 349
  • 530
18

There is a NPM package named "events" which makes you able to make event emitters in a browser environment.

const EventEmitter = require('events')
 
const e = new EventEmitter()
e.on('message', function (text) {
  console.log(text)
})
e.emit('message', 'hello world')

in your case, it's

const EventEmitter = require('events')

const e = new EventEmitter();
e.on('happy', function() {
    console.log('good');
});
e.emit('happy');
NanderTGA
  • 5
  • 1
  • 6
Amir Gorji
  • 2,809
  • 1
  • 21
  • 26
  • 7
    OP is looking for a working on a native implementation on a browser not requiring a npm package. – Ivan Oct 09 '20 at 07:42
  • 10
    @Ivan SO answers aren't just for the OP. This is the answer I was looking for, and looking at the votes, other people too. I want the same code to work in both environments. – Inigo Apr 10 '22 at 03:34
  • 1
    @Inigo In that case, you should have looked up or asked a different question which targets your requirement specifically. This answer, although correct, is just noise because it doesn't address what the OP is asking for. – vighnesh153 Aug 19 '23 at 04:39
  • @vighnesh153 I would argue that inundating SO with questions that differ in ***very tiny ways*** adds noise. Better to collect a variety of solutions to the crux of a problem under one question. Not to mention that the wording of the question, what people see in search results, "Is there any EventEmitter" instead of "Is there something like node's EventEmitter in browsers", if anything makes this the more valid answer. Not sure why you're nit-picking. – Inigo Aug 19 '23 at 11:36
11

This is enough for given case.

class EventEmitter{
    constructor(){
        this.callbacks = {}
    }

    on(event, cb){
        if(!this.callbacks[event]) this.callbacks[event] = [];
        this.callbacks[event].push(cb)
    }

    emit(event, data){
        let cbs = this.callbacks[event]
        if(cbs){
            cbs.forEach(cb => cb(data))
        }
    }
}

Update: I just published little bit more evolved version of it. It is very simple yet probably enough: https://www.npmjs.com/package/alpeventemitter

Alparslan
  • 111
  • 1
  • 3
  • Typescript: `callbacks: { [s: string]: ((...args: any[]) => any)[] }` – Mathieu CAROFF Jan 04 '20 at 11:43
  • 2
    This will result in out-of-order event handling when a event handler emits another event. You probably want to call the callbacks in emit after it returns, with setTimeout. Or put the events in a queue. – Vinicius Fortuna Jul 28 '21 at 16:39
6

Create a customized event in the client, and attach to dom element:

var event = new Event('my-event');

// Listen for the event.
elem.addEventListener('my-event', function (e) { /* ... */ }, false);

// Dispatch the event.
elem.dispatchEvent(event);

This is referred from: https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events Thanks Naeem Shaikh

Xin
  • 33,823
  • 14
  • 84
  • 85
  • 11
    This only works on HTML elements, the question is how to make it work on an object like you can do in Node. – Kokodoko Sep 23 '19 at 12:00
  • If you want to attach data within the dispatching event, you have to provide that in the `detail` property of the event object. So you can receive it from the other end by accessing that property in the `event` object on the listener. – Jayanga Jayathilake Aug 25 '23 at 05:13
3

I ended up using this:

export let createEventEmitter = () => {
   let callbackList: (() => any)[] = []

   return {
      on(callback: () => any) {
         callbackList.push(callback)
      },
      emit() {
         callbackList.forEach((callback) => {
            callback()
         })
      },
   }
}

Mathieu CAROFF
  • 1,230
  • 13
  • 19
1

2022 update: The BroadcatsChannel may provide a solution.

https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API

Nir
  • 1,225
  • 12
  • 8
  • Only for web workers... – smac89 Sep 02 '22 at 03:48
  • it works just fine in browser too. in MDN it says : "The Broadcast Channel API allows basic communication between browsing contexts (that is, windows, tabs, frames, or iframes) and workers on the same origin." – Nir Sep 03 '22 at 05:06
  • While this is a great solution (especially for communicating between tabs and web/service workers), it also has more overhead than a simple event target, because it has the additional task of communicating between processes. If your goal is to create an event listener within a single page, Brad's answer is probably the best option. – THEtheChad Nov 09 '22 at 06:23
1

I have created an npm package that do the same. You can use in Javascript or Typescript event-emitter

Example

import { EventEmitter } from 'tahasoft-event-emitter';

const onStatusChange = new EventEmitter();

function updateStatus() {
  // ...
  onStatusChange.emit();
}


// somewhere else, we want to add a listener when status change
onStatusChange.add(() => {
  // ...
});

Zuhair Taha
  • 2,808
  • 2
  • 35
  • 33
1

A very basic solution

function createEventEmitter() {
    const events = {}
    return {
        on: (name, listener) => {
            const listeners = events.hasOwnProperty(name)
                ? events[name]
                : (events[name] = [])
            listeners.push(listener)
        },
        emit: (name, ...params) => {
            const listeners = events[name] || []
            listeners.forEach((listener) => {
                listener(...params)
            })
        },
    }
}

const emitter = createEventEmitter()
emitter.on('myevent', (a, b) => {
    console.log(a, b)
})
emitter.emit('myevent', 1, 2)
Enzo
  • 4,111
  • 4
  • 21
  • 33
0

I like the answer from Alparslan above. Here's one that uses the browser CustomEvent.

let EventEmitter = (function () {

    let elem = document.createElement("div")
 
    return {
        on: function (name, cb) {
            elem.addEventListener(name, (e) => cb(e.detail), false )
        },
        emit: function (name, data) {
            elem.dispatchEvent(new CustomEvent(name, {detail: data}))
        } 
    }

})()
Henry
  • 2,870
  • 1
  • 25
  • 17
0

Here is a complete code for implementing the EventEmitter class that can be used both in the browser and in node applications. However, I recommend modifying & testing it well before using it.

import { EventEmitter as NodeEventEmitter } from 'events';

type Callback = (params: any) => void | Promise<void>;

const wrapFunction =
    (fn: Callback): Callback =>
    (params: CustomEvent) =>
        fn(params.detail);

export class InBrowserEventEmitter extends EventTarget {
    private _listeners: Record<string, [key: Callback, value: Callback][]> = {};
    private maxListeners = Number.MAX_SAFE_INTEGER;

    public on(eventName: string, fn: Callback) {
        this.addEventListener(eventName, fn);
        return this;
    }

    public once(eventName: string, fn: Callback) {
        const onceCallback = async (params: CustomEvent) => {
            await fn(params);
            this.off(eventName, onceCallback);
        };
        return this.on(eventName, onceCallback);
    }

    public off(eventName: string, fn: Callback) {
        this.removeEventListener(eventName, fn);
        return this;
    }

    public emit(eventName: string, params: unknown) {
        const event = new CustomEvent(eventName, { detail: params });
        return super.dispatchEvent(event);
    }

    public listenerCount(eventName: string): number {
        const eventListeners = this._listeners[eventName];
        return eventListeners ? eventListeners.length : 0;
    }

    public listeners(eventName: string): Callback[] {
        return this._listeners[eventName].map(value => value[0]) || [];
    }

    public eventNames(): string[] {
        return Object.keys(this._listeners);
    }

    public removeAllListeners() {
        this._listeners = {};
        return this;
    }

    public setMaxListeners(maxListeners: number) {
        this.maxListeners = maxListeners;
        return this;
    }

    public getMaxListeners(): number {
        return this.maxListeners;
    }

    public addEventListener(eventName: string, fn: Callback) {
        const wrappedFn = wrapFunction(fn);
        super.addEventListener(eventName, wrappedFn);
        if (!this._listeners[eventName]) {
            this._listeners[eventName] = [];
        }
        this._listeners[eventName].push([fn, wrappedFn]);
    }

    public removeEventListener(eventName: string, fn: Callback) {
        const eventListeners = this._listeners[eventName];
        if (eventListeners) {
            const index = eventListeners.findIndex(item => item[0] == fn);
            if (index !== -1) {
                super.removeEventListener(eventName, eventListeners[index][1]);
                eventListeners.splice(index, 1);
            }
        }
    }
}
// eslint-disable-next-line import/no-mutable-exports
export let EventEmitter: typeof NodeEventEmitter;
if (typeof window === 'undefined') {
    EventEmitter = NodeEventEmitter;
} else {
    // Fallback for the browser environment
    EventEmitter = InBrowserEventEmitter as unknown as typeof NodeEventEmitter;
}

Written with help from an AI Language Modle :-)

Muhammad Altabba
  • 2,583
  • 19
  • 31
-3

You need a JavaScript library, like this https://github.com/Olical/EventEmitter

JiangangXiong
  • 2,326
  • 1
  • 12
  • 14
-4

Node gained a native EventTarget in Node 15 (Oct 2020;) this question no longer applies

https://nodejs.org/api/events.html#eventtarget-and-event-api

John Haugeland
  • 9,230
  • 3
  • 37
  • 40