Type Inference for Node.js Event Emitters

When building a Node.js library with TypeScript you may end up using EventEmitter class. One of the challenges you will encounter is how to add type support to the various events that are emitted by your class.

This article, as of early 2020, shares the technique I've been using. There me be other or better ways, so feel free to share in the comments.

Consider a example class that is an EventEmitter called Peer. It has several methods that emit a variety of events. Each of these events have different payloads (sometimes no payload). Ideally, when using the IDE and when running TypeScript type checking, we would ensure that we are handling events in a strongly-typed manner.

export class Peer extends EventEmitter {  
  close() {
     // some code
     this.emit("close");
  }

  processMessage() {
    try {
      // some code
      this.emit("message", msg as IWireMessage);
    } catch(ex) {
      this.emit("error", ex);
    }
  }
}

We've defined three events with handlers as follows:

"close" : () => void
"message": (msg: IWireMessage) => void
"error": (err: Error) => void

The technique I've been using to add typing support is to declare an interface matching the type that is defined. You can then define methods for each of your events:

export declare interface Peer {  
  on(event: "close", listener: () => void): this;
  on(event: "error", listener: (err: Error) => void): this;
  on(event: "message", listener: (msg: IWireMessage) => void): this;
}

This code will give you type checking support if you use the above class like:

const peer = new Peer();  
peer.on("error", err => console.error(err));  
peer.on("message", msg => console.log("new message", msg);  

The downside is that if you want support for all event emitters methods you will need to create definitions for all of these EventEmitter methods. If you have a large number of events, this can get unwieldy quickly.

addListener  
listenerCount  
off  
on  
once  
prependListener  
prependOnceListener  
removeAllListeners  
removeListener  
rawListeners  
comments powered by Disqus