Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding IDE code completion support for custom event emitter functions? #241

Open
RickKukiela opened this issue Oct 31, 2021 · 3 comments
Open

Comments

@RickKukiela
Copy link

RickKukiela commented Oct 31, 2021

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.

@isRostCompany
Copy link

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

@dgolovin-dev
Copy link

dgolovin-dev commented May 1, 2023

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

@KeyboardRage
Copy link

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()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants