skip to Main Content

Code

Here is my solution for the problem 2694: EventEmitter:

type Callback = (...args: any[]) => any;
type Subscription = {
  unsubscribe: () => void;
};
type StampedCallback = { callback: Callback; timestamp: number };

class EventEmitter {
  events: Record<string, StampedCallback[]> = {};
  subscribe(eventName: string, callback: Callback): Subscription {
    if (!this.events[eventName]) this.events[eventName] = [];
    const timestamp = Date.now();
    this.events[eventName].push({ callback: callback, timestamp: timestamp });
    
    // Pay attention to this console.log() here
    console.log();
    
    return {
      unsubscribe: () => {
        this.events[eventName] = this.events[eventName].filter(
          (stampedCallback) => stampedCallback.timestamp !== timestamp,
        );
      },
    };
  }

  emit(eventName: string, args: any[] = []): any[] {
    return (
      this.events[eventName]?.map(({ callback }) => callback(...args)) ?? []
    );
  }
}

and some test code

const emitter = new EventEmitter();
const sub1 = emitter.subscribe("firstEvent", (x) => x + 1);
const sub2 = emitter.subscribe("firstEvent", (x) => x + 2);
sub1.unsubscribe(); // undefined
console.log("Output:", emitter.emit("firstEvent", [5])); // [7]

Here is the code in official TypeScriptPlayground

My Question

For the code in the current state output on my machine is this:




Output: [ 7 ]

in playground:

[LOG]: 
[LOG]: 
[LOG]: "Output:",  [7] 

As you can see, it outputs an array with 7 which is the correct output for this testcase

However, if I comment out the console.log() (line 13 in playground) I get:

Output: []

on my machine or:

[LOG]: "Output:",  [] 

in playground.
I do not understand, how can a single console.log() change the behaviour of the program this much. I also do not know how to reproduce the issue in simpler code.

2

Answers


  1. Chosen as BEST ANSWER

    As was pointed out in the comments, the problem is that without console.log() both subscriptions happen "at the same time" so their timestamps are the same. So using Date.now() is most likely not a viable way to stamp callbacks. Here is an updated solution for the 2694 LeetCode problem that uses Math.random() instead.


  2. The behavior you’re observing is related to the asynchronous nature of JavaScript and how the console.log() affects the timing of execution. The issue is not with the EventEmitter implementation itself but with how console.log() impacts the asynchronous execution of the code.

    Let’s break down what’s happening step by step:

    1. When you have the console.log() statement in the code (line 13), it introduces a slight delay in execution, which allows the "firstEvent" callbacks to be successfully registered before they are executed.

    2. With the console.log() statement present, the "firstEvent" callbacks (subscribers) are correctly added to the events object, and when you call emitter.emit("firstEvent", [5]), it triggers the callbacks, resulting in the correct output of [7].

    3. However, when you comment out the console.log() statement, the code executes faster since there is no delay introduced by the console.log(). This results in the "firstEvent" callbacks being registered and immediately removed (unsubscribed) before they have a chance to execute.

    Let’s see the sequence of events without the console.log():

    • You call emitter.subscribe("firstEvent", (x) => x + 1), and it adds the callback to events["firstEvent"] array.
    • You call emitter.subscribe("firstEvent", (x) => x + 2), and it adds the callback to events["firstEvent"] array.
    • You call sub1.unsubscribe(), which removes the first callback you added earlier from the events["firstEvent"] array.
    • Now, you call emitter.emit("firstEvent", [5]), but since the second callback (x => x + 2) was registered and removed before the emit call, it won’t be executed, resulting in an empty array [].

    To address this issue, you might want to consider an alternative approach to delay the execution slightly, allowing the callbacks to be registered before the emit() call takes place. One way to do this is by using setTimeout() with a delay of 0:

    class EventEmitter {
      // ... (same implementation)
    
      subscribe(eventName: string, callback: Callback): Subscription {
        // ... (same implementation)
    
        setTimeout(() => {
          console.log(); // Introducing a slight delay
        });
    
        return {
          unsubscribe: () => {
            // ... (same implementation)
          },
        };
      }
    }
    

    By using setTimeout with a delay of 0, you ensure that the console.log() is executed asynchronously after the subscribe() function has finished its execution. This allows the callbacks to be registered before the emit() call takes place, giving you the correct output even without the console.log() statement.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search