Why We need EventEmitter?
2025-6-22
| 2025-6-22
Words 2346Read Time 6 min
type
status
date
slug
summary
tags
category
icon
password

观察者模式

大家都很熟悉,我来给一个常见的实现方式。

一个常见的业务实现

可以看到事件处理的代码是和业务代码混合在一起的,因此自然会有一个想法,把事件event相关的代码收口到一起这样
因此 业界有这样的实现 EventEmitter 时间发射器类,来收口掉事件相关的处理

vscode 的EventEmitter 类

分析下代码
The repository implements its own event‑dispatching utility in src/vs/base/common/event.ts.
The main class is Emitter<T> which stores listeners, manages delivery and supports leak monitoring:
Adding listeners constructs a disposable wrapper so removal is automatic. It also hooks optional callbacks for the first/last listener:
Events are fired via fire(event: T) which walks the listener list in order and handles a delivery queue:
On the browser side, DomEmitter wraps DOM events and exposes them via the same Emitter interface:
For convenience when binding DOM events directly, the helper addDisposableListener creates a listener and returns a disposable:
A typical module creates an emitter, exposes the .event for clients to subscribe, and fires events internally, with disposal managed via _register:

Design Approach

Emitter implements a publish–subscribe (observer) pattern. Components expose events (via Emitter.event) to which other parts of the system subscribe. The design is lightweight and typed—listeners receive type‐specific payloads, and each subscription returns a disposable object.

Advantages

  • Unified event interface across browser, node and core modules using TypeScript’s generics.
  • Automatic cleanup via disposables: listeners can be removed easily and avoid leaks.
  • Optional leak warnings and profiling (leakWarningThresholdEventProfiling) help track misbehaving emitters.
  • Extra utilities such as AsyncEmitterPauseableEmitterDebounceEmitter and EventMultiplexer provide debouncing, pausing, asynchronous dispatch and event aggregation.

Problems Addressed

It abstracts away platform differences and reduces boilerplate when managing listeners. Disposal and leak detection solve issues with unbounded listeners over long-running sessions. The typed API helps catch event mismatches at compile time.

Typical Usage

  1. Create an emitter:
    1. Consumers subscribe via .event and receive a disposable:
      1. The owner calls _onSomething.fire(payload) to dispatch.
      1. Dispose of the subscription (disp.dispose()) or dispose the emitter’s owner to clean up automatically.
      This pattern is repeated throughout the codebase to implement model events, UI events and service notifications.
       

      为什么需要EventEmitter?

      Emitter 是仓库中实现的一套统一事件机制,它的目的主要有三点:
      1. 平台通用
        1. VS Code 需要在浏览器、Node、Electron 等环境运行,不想只依赖 Node 的 EventEmitter。 Emitter 只用简单的 TypeScript/JavaScript 代码就能在所有环境工作,同时还能桥接 DOM 事件(DomEmitter),因此适用于 VS Code 的多种运行模式。
      1. 类型与清理
        1. Emitter<T> 使用泛型,可以为每种事件指定确切的 payload 类型,避免在调用时出错。
          每次订阅都会返回一个 IDisposable,在销毁组件或取消监听时只需 dispose() 即可统一清理,减少遗漏监听器导致的内存泄漏。
      1. 扩展能力
        1. 该实现提供了去抖、暂停、异步触发等衍生类 (DebounceEmitterPauseableEmitterAsyncEmitter) 以及多路复用器 EventMultiplexer 等,便于在复杂场景下按需使用。同一套接口可以在不同地方复用,从逻辑事件到 DOM 事件都保持一致。
      因此,使用 Emitter 既能在不同平台下统一事件处理方式,又能自动管理监听器生命周期并提供丰富的扩展,解决了传统手动绑定/解绑事件容易产生的维护与泄露问题。
    2. design
    3. 浮点数的编码最长递增子序列
      Loading...