EventEmitter
浏览器中可用的事件触发器
ts
export type Off = () => void
type EventMap = Record<string, unknown[]> // 事件映射:事件名 -> 参数数组/元组
interface EventListener<T extends unknown[] = unknown[]> {
fn: (...args: T) => void
once: boolean
context: unknown
}
type Listeners<T extends EventMap> = {
[K in keyof T]?: Array<EventListener<T[K]>>
}
const listenersSymbol = Symbol('listeners')
export class EventEmitter<T extends EventMap = {}> {
private [listenersSymbol]: Listeners<T> = {}
// 监听事件
on<K extends keyof T>(
event: K,
listener: (...args: T[K]) => void,
context?: unknown,
once = false,
): Off {
if (!this[listenersSymbol][event]) {
this[listenersSymbol][event] = []
}
this[listenersSymbol][event].push({
fn: listener,
once,
context: context || this, // 默认使用当前实例作为上下文
})
// 返回取消监听函数
return () => this.off(event, listener)
}
// 监听一次事件
once<K extends keyof T>(
event: K,
listener: (...args: T[K]) => void,
context?: unknown,
): Off {
return this.on(event, listener, context, true)
}
// 取消监听
off<K extends keyof T>(
event: K,
listener: (...args: T[K]) => void,
): void {
const listeners = this[listenersSymbol][event]
if (!listeners) return
const index = listeners.findIndex((el) => el.fn === listener)
if (index === -1) return
listeners.splice(index, 1)
if (listeners.length === 0) {
// 如果该事件没有任何监听器了,则删除该事件
delete this[listenersSymbol][event]
}
}
// 触发事件
emit<K extends keyof T>(event: K, ...args: T[K]): void {
const listeners = this[listenersSymbol][event]
if (!listeners) return
const onceListeners: EventListener[] = []
// 避免在事件处理函数中修改 listeners
const _listeners = [...listeners]
for (let i = 0; i < _listeners.length; i++) {
const { fn, once, context } = _listeners[i]
try {
fn.apply(context, args)
} catch (error) {
console.error('Error in event handler:', error)
}
if (once) {
// @ts-ignore
onceListeners.push(_listeners[i])
}
}
// 移除只监听一次的监听器
if (this[listenersSymbol][event]) {
const listeners2 = this[listenersSymbol][event]
for (const listener of onceListeners) {
listeners2.splice(listeners2.indexOf(listener), 1)
}
if (listeners2.length === 0) {
delete this[listenersSymbol][event]
}
}
}
// 等待事件满足条件
waitForEvent<K extends keyof T>(
event: K,
predicate: (...args: T[K]) => boolean,
timeout?: number,
): Promise<T[K]> {
return new Promise((resolve, reject) => {
let timer: ReturnType<typeof setTimeout> | undefined
// 监听指定事件
const off = this.on(event, (...args: T[K]) => {
try {
if (predicate(...args)) {
if (timer !== void 0) {
clearTimeout(timer)
}
off()
resolve(args)
}
} catch (error) {
// predicate 抛出异常时,移除监听并拒绝 Promise
if (timer !== void 0) {
clearTimeout(timer)
}
off()
reject(error)
}
})
// 设置超时
if (timeout !== void 0) {
timer = setTimeout(() => {
off() // 超时后移除监听
reject(new Error(`Timeout waiting for event: ${String(event)}`))
}, timeout)
}
})
}
}