typescript LeetCode“2694的解决方案中console.log()的奇怪行为,事件发射器”

jhkqcmku  于 2023-08-08  发布在  TypeScript
关注(0)|答案(2)|浏览(112)

代码

下面是我对问题**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)) ?? []
    );
  }
}

字符串
和一些测试代码

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]


下面是官方TypeScriptPlayground中的代码

我的问题

对于我的机器上的当前状态输出的代码是这样的:

Output: [ 7 ]


在Playground:

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


正如您所看到的,它输出了一个带有7的数组,这是这个测试用例的正确输出
但是,如果我注解掉console.log()(playground中的第13行),我会得到:

Output: []


在我的机器上或:

[LOG]: "Output:",  []


在操场上。我不明白,一个console.log()怎么能改变程序的行为这么多。我也不知道如何用更简单的代码重现这个问题。

nukf8bse

nukf8bse1#

您观察到的行为与JavaScript的异步特性以及console.log()如何影响执行时间有关。问题不在于EventEmitter实现本身,而在于console.log()如何影响代码的异步执行。
让我们一步一步地分解正在发生的事情:
1.当代码中有console.log()语句时(第13行),它会在执行中引入一点延迟,这允许“firstEvent”回调在执行之前成功注册。
1.使用console.log()语句,“firstEvent”回调(订阅者)被正确地添加到events对象中,当您调用emitter.emit("firstEvent", [5])时,它触发回调,导致[7]的正确输出。
1.但是,当注解掉console.log()语句时,代码执行得更快,因为console.log()没有引入延迟。这会导致“firstEvent”回调被注册,并在它们有机会执行之前立即删除(取消订阅)。
让我们看看没有console.log()的事件序列:

  • 您调用emitter.subscribe("firstEvent", (x) => x + 1),它将回调添加到events["firstEvent"]数组。
  • 调用emitter.subscribe("firstEvent", (x) => x + 2),它会将回调函数添加到events["firstEvent"]数组。
  • 调用sub1.unsubscribe(),这将从events["firstEvent"]数组中删除前面添加的第一个回调。
  • 现在,您调用emitter.emit("firstEvent", [5]),但由于第二个回调(x => x + 2)在emit调用之前已注册并删除,因此不会执行,从而导致空数组[]

要解决这个问题,您可能需要考虑一种替代方法来稍微延迟执行,允许在emit()调用发生之前注册回调。一种方法是使用setTimeout(),延迟为0:

class EventEmitter {
  // ... (same implementation)

  subscribe(eventName: string, callback: Callback): Subscription {
    // ... (same implementation)

    setTimeout(() => {
      console.log(); // Introducing a slight delay
    });

    return {
      unsubscribe: () => {
        // ... (same implementation)
      },
    };
  }
}

字符串
通过使用延迟为0的setTimeout,可以确保console.log()在subscribe()函数执行完毕后异步执行。这允许在emit()调用发生之前注册回调,即使没有console.log()语句,也能提供正确的输出。

tyu7yeag

tyu7yeag2#

正如在评论中指出的那样,问题是如果没有console.log(),两个订阅“同时”发生,所以它们的时间戳是相同的。因此,使用Date.now()很可能不是标记回调的可行方法。下面是更新后的**solution for the 2694 LeetCode problem**,它使用了Math.random()

相关问题