Enabling Event bus suggestions for Typescript: A step-by-step guide

Hello, I've encountered an issue while attempting to add types for the TinyEmitter library. Specifically, I need to define two methods. First:

addEventListener(e: string, (...args: any[]) => void): void;

Second:

emit(e: string, ...args: any[]): void;

However, the current solution does not provide suggestions for event names and parameters.

I am looking to establish event types and event arguments, like so:

type EventMap = [
 (e: 'event1', arg1: number, arg2: string, arg3: string) => void,
 (e: 'event2', arg1: string, arg2: string) => void,
 ...
];

I have managed to infer the event name using:

type EventParam<I extends number = number> = Parameters<EventMap[I]>[0];

This will determine the event type ('event1' | 'event2')

For other parameters, I have attempted the following:
type EventArgs<I extends number = number> = EventMap[I] extends ((e: EventParam<I>, ...args: infer P) => any ? P : never;

How can I incorporate these definitions for the addEventListener and emit functions with type suggestions?

Any guidance on this matter would be greatly appreciated.

Answer №1

In my opinion, the best approach would be to create a utility interface that connects the event name (as a key) with the corresponding list of argument types (as a value):

interface EventArgs {
    event1: [number, string, string];
    event2: [string, string];
    // ...
}

After defining this interface, you can then customize your addEventListener() and emit() methods by using generics in K, where the type of the e argument is constrained to be a key within EventArgs:

interface Foo {
    addEventListener<K extends keyof EventArgs>(e: K, cb: (...args: EventArgs[K]) => void): void;
    emit<K extends keyof EventArgs>(e: K, ...args: EventArgs[K]): void;
}

I've named the interface Foo for reference, as it wasn't specified initially in the query. Assuming you have an implementation that creates a Foo instance:

const foo: Foo = makeFoo();

You can see how it functions as intended:

// Successful calls
foo.addEventListener("event1", (n, s1, s2) => {
    console.log(n.toFixed(2), s1.toUpperCase(), s2.toLowerCase()); // works fine
});
foo.emit("event2", "Abc", "Def"); // works fine
foo.emit("event1", Math.PI, "Abc", "Def"); // works fine

// Unsuccessful calls
foo.emit("event1", "Abc", "Def"); // error! 
foo.addEventListener("event2", (n, s1, s2) => { // error! 
    console.log(n.toFixed(2), s1.toUpperCase(), s2.toLowerCase())
});

This essentially addresses the question concerning typings. Here's one way to implement it with a good level of type safety:

function makeFoo(): Foo {
    const listenerMap: { [K in keyof EventArgs]?: ((...args: EventArgs[K]) => void)[] } = {}
    const ret: Foo = {
        addEventListener<K extends keyof EventArgs>(e: K, cb: (...args: EventArgs[K]) => void) {
            const listeners: ((...args: EventArgs[K]) => void)[] = listenerMap[e] ??= [];
            listeners.push(cb);
        },
        emit<K extends keyof EventArgs>(e: K, ...a: EventArgs[K]) {
            const listeners: ((...args: EventArgs[K]) => void)[] = listenerMap[e] ?? [];
            listeners.forEach(cb => cb(...a))
        }
    }
    return ret;
}

This implementation holds an object map that associates event names with arrays of event listeners. It adds the listener to the correct array when using addEventListener and invokes the listeners from the appropriate array in the emit method.

Link to code on TypeScript Playground

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Retrieve the chosen item to automatically fill in the input fields using Ionic 2 and Angular

My goal is to create a dropdown menu populated with a list of items, and when a product is selected, its price should automatically appear in the quantity field. <ion-item> <ion-label>Item</ion-label> <ion-select (ionChange)="getP ...

What is the reason this switch statement functions only with one case?

Why is the switch statement not functioning properly? It seems to correctly identify the values and match them with the appropriate case, but it only works when there is a single case remaining. If there are multiple cases to choose from, nothing happens. ...

"Clicking on a jQuery div will cause it to slide down, and clicking again

I am currently working on this code snippet: $("#right").click(function() { $("#signin").stop().slideDown(300); }); At the moment, it drops down when clicked, but I would like it to slideUp() when clicked again. Your help is appreciated. On a relate ...

Encountering an issue with Firebase Cloud Messaging

I am struggling to implement a push notification trigger whenever a document field is updated. As a newcomer to node.js, I am facing challenges in debugging what seems like a simple function. Below is the code snippet: // Cloud Functions for Firebase SDK ...

Can someone explain the meaning of the paragraph in the "Index Routes" section of the React-Router documentation?

Lesson 8 of the React-Router tutorial delves into the concept of "Index Routes", outlining the importance of structuring routes in a specific way. Here are some key points from their discussion: The tutorial suggests that while setting up the initial rout ...

I'm curious if it's possible to superimpose a png image and specific coordinates onto a map by utilizing react-map

I am attempting to showcase a png graphic on a react-map-gl map, following the approach outlined here. Unfortunately, the image is not appearing as expected and no error messages are being generated for me to troubleshoot. Below is the snippet of code I&a ...

Exploring unescaped XML for handling unicode characters

Currently tackling the challenge of parsing some XML that is not properly formatted. The XML file contains un-escaped ampersands, which goes against the standard rules for XML files. My goal is to extract unicode formatted phrases from this XML file whil ...

A generic type in TypeScript that allows for partial types to be specified

My goal is to create a type that combines explicit properties with a generic type, where the explicit properties have priority in case of matching keys. I've tried implementing this but encountered an error on a specific line - can anyone clarify why ...

Angular 2 Directive for Ensuring Required Conditions

Is there a way to make form fields required or not based on the value of other fields? The standard RequiredValidator directive doesn't seem to support this, so I've created my own directive: @Directive({ selector: '[myRequired][ngControl ...

The issue of Bootstrap dynamic tabs retaining their active state even after switching tabs, leading to elements being stacked

For my university project, I am developing a website that resembles a text editor using Bootstrap as the framework. To create the website menus, dynamic tabs have been utilized. The following is the code snippet I have implemented: <!--Bootstrap ...

React typescript is handling various promise response types, causing strange behavior in type-checking

I recently started utilizing the and I seem to be encountering a perplexing issue. If further context is needed, please let me know and I will provide it. All the necessary functions and types are mentioned below my explanatory paragraphs. Your assistance ...

Navigating through an ajax-based webpage entirely with selenium webdriver

I have attempted to scroll a page entirely using the following code: var scrollToBottom = function() { window.scrollTo(0, Math.max(document.documentElement.scrollHeight, document.body.scrollHeight, document.documentElement.clientHeight)); }; window.on ...

AngularJS: resolving route dependencies

I have a variable $scope.question that contains all the questions for the page. My goal is to loop through the questions page by page. To achieve this, I created a function called questionsCtrl and I am calling this function in the config while setting up ...

Utilizing Node.js to match arrays

let items = ["Product",["car","Bike"]]; let result = items[1].map((item) => [items[0], item]); console.log(result); My current output is: [["Product" , "car"], ["Product" , "bike"]], The above code works for this simple output, but I'm unsure ho ...

Converting an array of objects into a flat array of objects with Javascript

My data array requires conversion to a flattened array returning new header and data. For instance, the first object contains a name with three data points: "title, first, last." The desired output is: From : { gender: 'male', name: { ...

Unable to designate decimal value as the default in DropdownListFor in ASP.NET Core when utilizing JavaScript

Everything appears to be functioning properly, except when dealing with decimal values in the following code snippet: $('select#listspec_0__qty option[value = 105.3]').attr("selected", true); ...

Error: The function 'fetch' is not recognized in Selenium Console

Having some trouble with Selenium and Chrome Developer Tools. I want to open Selenium, go to a URL, and then use driver.execute_script to make a fetch request via Console in Chrome Developer Tools within the Selenium window. However, when I try to run thi ...

The Importance of Selenium Events and Patience

Currently, I am using Selenium to automate some testing for our company's website, but encountering issues along the way. TestItemFromSearch: (driver, part, qty) => { Search.SearchItem(driver, part); driver.findElement(By.id('enterQty ...

Sails.js seems to be malfunctioning, as it does not seem to be recognizing the term 'sails'

It seems like I'm encountering an issue with the 'sails' command not being recognized on my Windows 10 system. Despite following all the installation steps, including globally installing Sails.js through npm and ensuring Node is installed, I ...

Adapting the column width to display or hide content with CSS styling

I have a row with 2 columns. The left column contains my main content and the right column is a chatroom. I would like users to be able to minimize and open the chatroom, which I already know how to do. However, when the chatroom is open, I want the left ...