Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A very simple EventEmitter in pure JavaScript (suitable for both node.js and browsers).
/* Polyfill indexOf. */
var indexOf;
if (typeof Array.prototype.indexOf === 'function') {
indexOf = function (haystack, needle) {
return haystack.indexOf(needle);
};
} else {
indexOf = function (haystack, needle) {
var i = 0, length = haystack.length, idx = -1, found = false;
while (i < length && !found) {
if (haystack[i] === needle) {
idx = i;
found = true;
}
i++;
}
return idx;
};
};
/* Polyfill EventEmitter. */
var EventEmitter = function () {
this.events = {};
};
EventEmitter.prototype.on = function (event, listener) {
if (typeof this.events[event] !== 'object') {
this.events[event] = [];
}
this.events[event].push(listener);
};
EventEmitter.prototype.removeListener = function (event, listener) {
var idx;
if (typeof this.events[event] === 'object') {
idx = indexOf(this.events[event], listener);
if (idx > -1) {
this.events[event].splice(idx, 1);
}
}
};
EventEmitter.prototype.emit = function (event) {
var i, listeners, length, args = [].slice.call(arguments, 1);
if (typeof this.events[event] === 'object') {
listeners = this.events[event].slice();
length = listeners.length;
for (i = 0; i < length; i++) {
listeners[i].apply(this, args);
}
}
};
EventEmitter.prototype.once = function (event, listener) {
this.on(event, function g () {
this.removeListener(event, g);
listener.apply(this, arguments);
});
};
@mauriciosoares

This comment has been minimized.

Copy link

@mauriciosoares mauriciosoares commented Jun 15, 2014

Thanks man! very helpful... I'm going to use it in my library if you don't mind :)

@Xeoncross

This comment has been minimized.

Copy link

@Xeoncross Xeoncross commented Jul 10, 2014

What does var i, listeners, length, args = [].slice.call(arguments, 1); do? Why is that there?

Never mind, I just saw the commas and realized it is just initializing the variables except for the last args which is set to all the arguments of the function minus the event name.

@mykeels

This comment has been minimized.

Copy link

@mykeels mykeels commented Dec 11, 2017

This is awesome!!!

Borrowing from this, I wrote the eventify function which will turn any object into an event emitter:

const eventify = (self) => {
    self.events = {}

    self.on = function (event, listener) {
        if (typeof self.events[event] !== 'object') {
            self.events[event] = []
        }

        self.events[event].push(listener)
    }

    self.removeListener = function (event, listener) {
        let idx

        if (typeof self.events[event] === 'object') {
            idx = self.events[event].indexOf(listener)

            if (idx > -1) {
                self.events[event].splice(idx, 1)
            }
        }
    }

    self.emit = function (event) {
        var i, listeners, length, args = [].slice.call(arguments, 1);

        if (typeof self.events[event] === 'object') {
            listeners = self.events[event].slice()
            length = listeners.length

            for (i = 0; i < length; i++) {
                listeners[i].apply(self, args)
            }
        }
    }

    self.once = function (event, listener) {
        self.on(event, function g () {
            self.removeListener(event, g)
            listener.apply(self, arguments)
        })
    }
}

It can be used like:

const myEmitter = {}
eventify(myEmitter)
@Raphael909

This comment has been minimized.

Copy link

@Raphael909 Raphael909 commented Dec 21, 2017

Using lodash and es6

function eventMixin(obj) {
    obj._events = {};

    obj.on = (event, listener) => {
        if (_.isNil(obj._events[event])) {
            obj._events[event] = [];
        }
        obj._events[event].push(listener);
    };

    obj.emit = (event, ...args) => {
        if (_.isNil(obj._events[event])) {
            return;
        }
        _.forEach(obj._events[event], (listener) => {
            listener.apply(obj, args);
        });
    };

    obj.removeListener = (event, listener) => {
        if (_.isNil(obj._events[event])) {
            return;
        }
        _.pull(obj._events[event], listener);
    };

    obj.once = (event, listener) => {
        obj.on(event, function handler(...args) {
            obj.removeListener(event, handler);
            listener.apply(obj, args);
        });
    };
};
@sminutoli

This comment has been minimized.

Copy link

@sminutoli sminutoli commented Jun 18, 2018

Using ES2015 classes...

class EventEmitter {
  constructor() {
    this.events = {};
  }
  on(event, listener) {
      if (typeof this.events[event] !== 'object') {
          this.events[event] = [];
      }
      this.events[event].push(listener);
      return () => this.removeListener(event, listener);
  }
  removeListener(event, listener) {
    if (typeof this.events[event] === 'object') {
        const idx = this.events[event].indexOf(listener);
        if (idx > -1) {
          this.events[event].splice(idx, 1);
        }
    }
  }
  emit(event, ...args) {
    if (typeof this.events[event] === 'object') {
      this.events[event].forEach(listener => listener.apply(this, args));
    }
  }
  once(event, listener) {
    const remove = this.on(event, (...args) => {
        remove();
        listener.apply(this, args);
    });
  }
};

Or plain prototype with factory function...

const anEventEmitter = {
  events: {},
  on(event, listener) {
      if (typeof this.events[event] !== 'object') {
          this.events[event] = [];
      }
      this.events[event].push(listener);
      return () => this.removeListener(event, listener);
  },
  removeListener(event, listener) {
    if (typeof this.events[event] === 'object') {
        const idx = this.events[event].indexOf(listener);
        if (idx > -1) {
          this.events[event].splice(idx, 1);
        }
    }
  },
  emit(event, ...args) {
    if (typeof this.events[event] === 'object') {
      this.events[event].forEach(listener => listener.apply(this, args));
    }
  },
  once(event, listener) {
    const remove = this.on(event, (...args) => {
        remove();
        listener.apply(this, args);
    });
  }
};

const makeEventEmitter = () => ({
   __proto__: anEventEmitter,
   events: {}
});
@shirakaba

This comment has been minimized.

Copy link

@shirakaba shirakaba commented Jun 18, 2018

@sminutoli 's ES2015 classes implementation adapted to TypeScript, with a removeAllListeners() function added:

type Listener = (...args: any[]) => void
type Events = { [event: string]: Listener[] };

export class MyEventEmitter {
    private readonly events: Events = {};

    constructor() {
    }

    public on(event: string, listener: Listener): () => void {
        if(typeof this.events[event] !== 'object') this.events[event] = [];
        
        this.events[event].push(listener);
        return () => this.removeListener(event, listener);
    }

    public removeListener(event: string, listener: Listener): void {
        if(typeof this.events[event] !== 'object') return;
        
        const idx: number = this.events[event].indexOf(listener);
        if(idx > -1) this.events[event].splice(idx, 1);
    }

    public removeAllListeners(): void {
        Object.keys(this.events).forEach((event: string) => 
            this.events[event].splice(0, this.events[event].length)
        );
    }

    public emit(event: string, ...args: any[]): void {
        if(typeof this.events[event] !== 'object') return;

        this.events[event].forEach(listener => listener.apply(this, args));
    }

    public once(event: string, listener: Listener): void {
        const remove: (() => void) = this.on(event, (...args: any[]) => {
            remove();
            listener.apply(this, args);
        });
    }
}
@pmcalabrese

This comment has been minimized.

Copy link

@pmcalabrese pmcalabrese commented Jun 22, 2018

@shirakaba excellent code, a small improvement would be add a generic on the exported class like so

...
export class MyEventEmitter<T extends string> {
    private readonly events: Events = {};

    constructor() {
    }
...

When you create a new instance you can pass the event strings

const emitter = new MyEventEmitter<"start" | "done">;

so you can have type check and suggestions

@undecidedapollo

This comment has been minimized.

Copy link

@undecidedapollo undecidedapollo commented Jul 25, 2018

This issue is in the javascript version and the typescript version. The fix below is in typescript. There seems to be a problem with the once/removeListener function. When the event is emitted and the listener is called, it calls remove. The remove function splices the array stored at this.event[eventString] while the emit function is doing a forEach on the same array. This splice modifies the array while it is being iterated against, and causes the forEach to skip the next listener. A proposed solution to this would be:

    public emit(event: string, ...args: any[]): void {
        if (typeof this.events[event] !== "object") {
            return;
        }

        [...this.events[event]].forEach((listener) => listener.apply(this, args));
    }

The complete class:

type Listener = (...args: any[]) => void;
interface IEvents { [event: string]: Listener[]; }

export class EventEmitter {
    private readonly events: IEvents = {};

    public on(event: string, listener: Listener): () => void {
        if (typeof this.events[event] !== "object") {
            this.events[event] = [];
        }

        this.events[event].push(listener);
        return () => this.removeListener(event, listener);
    }

    public removeListener(event: string, listener: Listener): void {
        if (typeof this.events[event] !== "object") {
            return;
        }

        const idx: number = this.events[event].indexOf(listener);
        if (idx > -1) {
            this.events[event].splice(idx, 1);
        }
    }

    public removeAllListeners(): void {
        Object.keys(this.events).forEach((event: string) =>
            this.events[event].splice(0, this.events[event].length),
        );
    }

    public emit(event: string, ...args: any[]): void {
        if (typeof this.events[event] !== "object") {
            return;
        }

        [...this.events[event]].forEach((listener) => listener.apply(this, args));
    }

    public once(event: string, listener: Listener): () => void {
        const remove: (() => void) = this.on(event, (...args: any[]) => {
            remove();
            listener.apply(this, args);
        });

        return remove;
    }
}
@ianva

This comment has been minimized.

Copy link

@ianva ianva commented Aug 29, 2018

// Apply a ES6's new data structure Set

class EventEmitter{

  constructor(){
    this.events = {};
  }

  _getEventListByName(eventName){
    if(typeof this.events[eventName] === 'undefined'){
      this.events[eventName] = new Set();
    }
    return this.events[eventName]
  }

  on(eventName, fn){
    this._getEventListByName(eventName).add(fn);
  }

  once(eventName, fn){

    const self = this;

    const onceFn = function(...args){
      self.removeListener(eventName, onceFn);
      fn.apply(self, args);
    };
    this.on(eventName, onceFn);

  }

  emit(eventName, ...args){

    this._getEventListByName(eventName).forEach(function(fn){

      fn.apply(this,args);

    }.bind(this));

  }

  removeListener(eventName, fn){
    this._getEventListByName(eventName).delete(fn);
  }


}
@brianjenkins94

This comment has been minimized.

Copy link

@brianjenkins94 brianjenkins94 commented Aug 10, 2019

Another TypeScript version but with wildcard support:

export class EventEmitter {
	private events = {};

	public on(event, listener) {
		if (this.events[event] === undefined) {
			this.events[event] = [];
		}

		this.events[event].push(listener);

		return function() {
			this.off(event, listener);
		};
	}

	public off(event?, listener?) {
		if (event === undefined && listener === undefined) {
			this.events = {};
		} else if (listener === undefined) {
			delete this.events[event];
		} else if (this.events[event].indexOf(listener) !== -1) {
			this.events[event].splice(this.events[event].indexOf(listener), 1);
		}
	}

	public emit(event, ...args) {
		if (this.events[event] !== undefined) {
			for (const listener of this.events[event]) {
				listener(...args);
			}
		}

		if (event !== "*") {
			this.emit("*", ...args);
		}
	}

	public once(event, listener) {
		return this.on(event, () => {
			this.emit(event);

			this.off(event, listener);
		});
	}
}
@fend25

This comment has been minimized.

Copy link

@fend25 fend25 commented Oct 6, 2019

enhanced with wildcard support for partial wildcard (for example, for events users.requested' and users.loadedwe can subscribe tousers.orusers`)

type Listener = (...args: any[]) => void

interface IEvents {
  [event: string]: Listener[]
}

export class EventEmitter {
  private readonly events: IEvents = {}
  private wildcardEvents: string[] = []

  private recalcWildcardEvents() {
    const newWildCardEvents = []
    for (const i in this.events) {
      if (i.endsWith('*') && this.events[i] && this.events[i].length > 0) {
        newWildCardEvents.push(i)
      }
    }
    this.wildcardEvents = newWildCardEvents
  }

  public on(event: string, listener: Listener): () => void {
    if (typeof this.events[event] !== "object") {
      this.events[event] = []
    }

    this.events[event].push(listener)
    this.recalcWildcardEvents()
    return () => this.removeListener(event, listener)
  }

  public removeListener(event: string, listener: Listener): void {
    if (typeof this.events[event] !== "object") {
      return
    }

    const idx: number = this.events[event].indexOf(listener)
    if (idx > -1) {
      this.events[event].splice(idx, 1)
    }
    this.recalcWildcardEvents()
  }

  public removeAllListeners(): void {
    Object.keys(this.events).forEach((event: string) =>
      this.events[event].splice(0, this.events[event].length),
    )
    this.recalcWildcardEvents()
  }

  public emit(event: string, ...args: any[]): void {
    if (typeof this.events[event] === "object") {
      [...this.events[event]].forEach((listener) => listener.apply(this, args))
    }

    if (event !== "*") {
      this.emit("*", ...args)
    }

    for (const rawWcEvent of this.wildcardEvents) {
      const wcEvent = rawWcEvent.slice(0, rawWcEvent.endsWith('.*') ? -2 : -1)
      if (!event.endsWith('*') && event !== wcEvent && event.startsWith(wcEvent)) {
        this.emit(rawWcEvent, event)
      }
    }
  }

  public once(event: string, listener: Listener): () => void {
    const remove: (() => void) = this.on(event, (...args: any[]) => {
      remove()
      listener.apply(this, args)
      this.recalcWildcardEvents()
    })

    return remove
  }
}
@AntonioArts

This comment has been minimized.

Copy link

@AntonioArts AntonioArts commented Oct 24, 2019

Nice topic! A lot of interesting implementations

@laurengarcia

This comment has been minimized.

Copy link

@laurengarcia laurengarcia commented Dec 12, 2019

This is so awesome. Thank you all!

@feargswalsh92

This comment has been minimized.

Copy link

@feargswalsh92 feargswalsh92 commented Jan 17, 2020

Spent the day working on this as it was an interview question on Pramp that really piqued my interest. Still don't fully understand the eventEmitter, bit it's a lot clearer now. Would be grateful for some feedback on my implementation. It's definitely a little verbose.

class EventEmitter {
  constructor(event) {
    this._events = {};
  }

  on = (event, listener) => {
    if (typeof listener === "function") {
      this._events[event] = [];
      this._events[event].push(listener);
    } else {
      throw new Error(
        " The listener argument must be of type Function. Received type undefined"
      );
    }
    return this.eventEmitter;
  };

  // Adds a one time listener to the event. This listener is invoked only the next time the event is fired, after which it is removed.
  once = (event, listener) => {
    this._events[event].push({ listener: listener });
    // Returns emitter, so calls can be chained.
    return this.eventEmitter;
  };

  // Execute each of the listeners in order with the supplied arguments. Returns true if the event had listeners, false otherwise.
  // emit

  emit = (event, ...args) => {
    for (let i = 0; i < this._events[event].length; i++) {
      if (typeof this._events[event][i] === "function") {
        this._events[event][i](args);
      } else if (this._events[event][i] && this._events[event][i].listener) {
        this._events[event][i].listener(...args);
        delete this._events[event][i];
      }
    }
    if (this._events[event].length) {
      return true;
    }
    return false;
  };

  //Removes a listener from the listener array for the specified event. Caution − It changes the array indices in the listener array behind the listener. removeListener will remove, at most, one instance of a listener from the listener array. If any single listener has been added multiple times to the listener array for the specified event, then removeListener must be called multiple times to remove each instance. Returns emitter, so calls can be chained

  off = (event, responseToEvent) => {
    const eventArray = this._events[event];
    let i = 0;
    let deleteCount = 0;
    if (typeof eventArray !== "undefined") {
      while (deleteCount < 1) {
        // console.log(eventArray[i] && typeof eventArray[i] === 'function');
        if (typeof eventArray[i] === "function") {
          eventArray.splice(i, 1);
          deleteCount++;
        }
        i++;
      }
    }
    return this.eventEmitter;
  };
}
@alphakevin

This comment has been minimized.

Copy link

@alphakevin alphakevin commented Feb 19, 2020

Use Symbol as event list key to avoid naming conflict:

const eventsKey = Symbol('events')

class EventEmitter {
  constructor() {
    this[eventsKey] = {}
  }
  // ...
}
@garronej

This comment has been minimized.

Copy link

@garronej garronej commented Feb 19, 2020

You guys might want to check out EVT

image

The lib provides solution for things that can't easily be done with EventEmitter like:

  • Enforcing type safety.
  • Removing a particular listener when the callback is an anonymous function.
  • Adding a one-time listener for the next event that meets a condition.
  • Waiting (via a Promise) for one thing or another to happen.
    Example: waiting at most one second for the next message, stop waiting if the socket disconnects.
@jonathanbsilva

This comment has been minimized.

Copy link

@jonathanbsilva jonathanbsilva commented Jul 31, 2020

function EventEmitter() {
  const eventRegister = {};
  
  const on = (name, fn) => {
    if (!eventRegister[name]) eventRegister[name] = [];
    eventRegister[name].push(fn);
  }
  
  const trigger = (name) => {
    if (!eventRegister[name]) return false;
    eventRegister[name].forEach((fn) => fn.call());
  }
  
  const off = (name, fn) => {
    if (eventRegister[name]) {
      const index = eventRegister[name].indexOf(fn);
      if (index >= 0) eventRegister[name].splice(index, 1);
    } 
  }
  
  return {
    on, trigger, off
  }
}
@infinitum11

This comment has been minimized.

Copy link

@infinitum11 infinitum11 commented Sep 2, 2020

You might want to check this repo in order to find out how to create simple type safe event emitter library in Typescript.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.