Hone logo
Hone
Problems

Type-Safe Event Emitter in TypeScript

Building a robust event emitter is a common requirement in many applications. This challenge asks you to implement a type-safe event emitter in TypeScript, ensuring that event names and associated callback types are strictly enforced at compile time. This will lead to more maintainable and error-free code by preventing type-related issues at runtime.

Problem Description

You are tasked with creating a TypedEventEmitter class in TypeScript. This class should allow subscribers to register callbacks for specific event names, and the emitter should ensure that only callbacks of the expected type are registered for each event. The emitter should support multiple subscribers for the same event.

Key Requirements:

  • Type Safety: The event emitter must enforce type safety. When subscribing to an event, the callback function's type must match the expected type for that event.
  • Multiple Subscribers: Multiple subscribers should be able to listen to the same event.
  • Event Emission: The emitter should be able to trigger events, passing data to the registered callbacks. The data passed should match the expected type for the event.
  • Unsubscription: Subscribers should be able to unsubscribe from events.
  • Clear Interface: The class should have a clear and intuitive interface for subscribing, unsubscribing, and emitting events.

Expected Behavior:

  • When subscribing to an event, the TypeScript compiler should raise an error if the callback type does not match the expected type.
  • When emitting an event, the TypeScript compiler should raise an error if the data passed does not match the expected type.
  • Unsubscribing should remove the callback from the list of listeners for the specified event.
  • Emitting an event should call all registered callbacks for that event, in the order they were registered.

Edge Cases to Consider:

  • Attempting to subscribe with an incorrect callback type.
  • Attempting to emit an event with incorrect data.
  • Unsubscribing from an event that was never subscribed to.
  • Emitting an event with no subscribers.
  • Subscribing the same function multiple times to the same event.

Examples

Example 1:

interface UserEventData {
    userId: number;
    username: string;
}

const emitter = new TypedEventEmitter<"userCreated", UserEventData>();

const callback = (data: UserEventData) => {
    console.log(`User created: ${data.username}`);
};

const subscription = emitter.subscribe("userCreated", callback);

emitter.emit("userCreated", { userId: 123, username: "john.doe" }); // Output: User created: john.doe

emitter.unsubscribe(subscription);

try {
    emitter.emit("userCreated", { userId: 456, username: "jane.doe" });
} catch (error) {
    console.log("Error:", error.message); // Expected: No subscribers for event 'userCreated'
}

Explanation: A userCreated event is defined with data type UserEventData. A callback is registered, and the event is emitted with the correct data. After unsubscribing, attempting to emit the event results in an error.

Example 2:

interface MessageEventData {
    message: string;
}

const emitter = new TypedEventEmitter<"newMessage", MessageEventData>();

const incorrectCallback = (data: number) => { // Incorrect type
    console.log("Incorrect callback");
};

try {
    emitter.subscribe("newMessage", incorrectCallback);
} catch (error) {
    console.log("Error:", error.message); // Expected: Type 'number' is not assignable to type 'string'.
}

Explanation: Attempting to subscribe with a callback of the wrong type results in a compile-time error.

Example 3: (Edge Case - No Subscribers)

interface DataEventData {
    data: string;
}

const emitter = new TypedEventEmitter<"dataEvent", DataEventData>();

try {
    emitter.emit("dataEvent", { data: "some data" });
} catch (error) {
    console.log("Error:", error.message); // Expected: No subscribers for event 'dataEvent'
}

Explanation: Emitting an event with no subscribers throws an error.

Constraints

  • The solution must be written in TypeScript.
  • The TypedEventEmitter class should be well-documented and easy to understand.
  • The implementation should be reasonably efficient. While performance is not the primary concern, avoid unnecessary overhead.
  • The class should handle multiple subscriptions to the same event correctly.
  • The class should not rely on external libraries.
  • The code should be free of syntax errors and follow TypeScript best practices.

Notes

  • Consider using generics to achieve type safety.
  • Think about how to represent subscriptions and manage the list of listeners.
  • Error handling is important. Provide informative error messages when type mismatches or other issues occur.
  • The emit method should throw an error if there are no subscribers for the event.
  • The unsubscribe method should handle the case where the subscription is not found.
  • Focus on creating a robust and type-safe solution that adheres to the requirements outlined above.
Loading editor...
typescript