primus / eventemitter3

EventEmitter3 - Because there's also a number 2. And we're faster.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Adding IDE code completion support for custom event emitter functions?

RickKukiela opened this issue · comments

I've been using this package for an event heavy project I'm working on and its been great to work with. Thanks :) - I am struggling to figure out a way to add my own emitter definitions so my IDE understands when I start typing the .emit() code it can help me by showing me what parameters it will accept. Based on what I have seen other people doing with other libraries and what I gathered from looking at your source code this is my best attempt:

"use strict";

import {EventEmitter, EventListener, EventNames} from "eventemitter3";

export interface CEvents {
    'before-request': any[]; // based on your comments in the source code on extending interface EventTypes
}

export declare interface CEventEmitter {
    on<T extends EventNames<CEvents>>(
        event: T,
        fn: EventListener<CEvents, T>,
        context?: any
    ): this;

    // emit
}

export class CEventEmitter extends EventEmitter {
    constructor() {
        super();
    }
}

As you can see I haven't even been able to implement the on* method correctly since I still have the emit* section commented out. This is as close as I can get it and I pretty much hit a wall. My compiler gives me the following complaint with this setup (when hovering CEventEmitter of export declare interface CEventEmitter):

TS2430: Interface 'CEventEmitter' incorrectly extends interface 'EventEmitter<string | symbol, any>'.

Types of property 'on' are incompatible.
    Type '<T extends "before-request">(event: T, fn: (...args: ArgumentMap<EventTypes>[Extract<T, "before-request">]) => void, context?: any) => this' is not assignable to type '<T extends string | symbol>(event: T, fn: (...args: any[]) => void, context?: any) => this'.
      
    Types of parameters 'event' and 'event' are incompatible.
        Type 'T' is not assignable to type '"before-request"'.           
        Type 'string | symbol' is not assignable to type '"before-request"'.             
        Type 'string' is not assignable to type '"before-request"'.

I hope someone out there can see what I'm missing and help me out... I'm sorry if this is the wrong place for this post. I'm not sure where else I would put it besides stack overflow. I figured I would try here since this is kind niche. I'm not new to JavaScript but this is my first stab at a real TypeScript project so I'm trying to understand and learn how this works. Thank you in advance.

hello
not a maintainer or anything, just put some thoughts regarding your task and this is what I ended up with:

import { EventEmitter } from 'eventemitter3';


interface Events {
  event1: { payload1: string };
  event2: { payload2: number };
}

class TypedEmitter< Events = Record< string, unknown > > {
  __ee = new EventEmitter();

  emit< K extends keyof Events >(type: K, payload: Events[K]) {
    this.__ee.emit(String(type), payload);
  }

  on< K extends keyof Events >(type: K, handler: (payload: Events[K]) => unknown) {
    this.__ee.on(String(type), handler);
  }
}

const typedEmitterInstance = new TypedEmitter<Events>();


typedEmitterInstance.emit('event1', { payload1: '' });
typedEmitterInstance.on('event2', p => console.log(p.payload2));

IMHO, gives pretty decent autocomplete experience

My setup is

  • VSCode 1.74.2
  • eventemitter3@4.0.7
  • typescript v 4.9.4

Hi, guys.

I had the same problem with IDE support. So I found another solution - C#-like events.
It greatly improved the navigation and the editor hints.

I use VSCode with @js-doc, no Typescript.

There is an example how I define events:

class Kitty {
  /** @readonly */
  emitter = new EventEmitter();

  /** 
   * Event with 2 args: string, integer.
   * @type {TypedEvent<[string, integer]>}
   * @readonly
   */
  eventSay = new TypedEvent(this.emitter, "say");

  /** 
   * @param {string} word 
   * @param {integer} cnt
   */
  say(word, cnt) {
    this.eventSay.safeEmit(word, cnt); // editor shows the correct signature
  }
}

const kitty = new Kitty();

kitty.eventSay.on((w, c) => { throw new Error('unhandled') }); // it will be ignored due to safeEmit 
// editor shows the correct signature
kitty.eventSay.once((w, c) => console.log(`once say ${w}:${typeof(w)} ${c}:${typeof(c)}`)); 
kitty.eventSay.on((w, c) => console.log(`on say ${w}:${typeof(w)} ${c}:${typeof(c)}`));

kitty.say("meow", 3);

Repo link: https://github.com/dgolovin-dev/eventemitter3-typedevent

This is a bit old, but for others, here's how I solved it with TypeScript.
In my case I wanted a custom class that is itself just an emitter, so I do things like this.emit("...", ...) in methods.

import { EventEmitter } from "eventemitter3";

// The types of the arguments you'll pass
interface argOne {
    foo: string;
}
interface argTwo {
    bar: number;
}

// This will contain all possible events and the args those events will pass
interface MyCustomEvents {
    "my.event": [argOne, argTwo];
}

// A new event emitter that uses the custom events
class TypedEmitter extends EventEmitter<MyCustomEvents> {
    constructor(props) {
        super();
    }

    doThing(text: string) {
        const argA = {foo: text};
        const argB = {bar: 1};

        this.emit("my.event", argA, argB);
    }
}

const test = new TypedEmitter();

test.on("my.event", (argOne, argTwo) => {
    // argOne is of type argOne
    // argTwo is of type argTwo
});

// My IDE will yell at me in these, for the respective reasons:

// no such event
test.on("doesnt.exist", () => {})

// that's not an operation I can perform on the type argOne is
test.on("my.event", (argOne) => {
    argOne++;
});

// you don't receive that many args
test.on("my.event", (argOne, argTwo, argThree) => {

});

// It will also yell at me for the same reasons when using .emit()
  • WebStorm 2023.3.4
  • TypeScript 5.2.2
  • eventemitter3@5.0.1