diff --git a/apps/ai-game-creator-shell/tests/unhandledRejectionBridge.ts b/apps/ai-game-creator-shell/tests/unhandledRejectionBridge.ts index 85dc4c24e..c61cabea4 100644 --- a/apps/ai-game-creator-shell/tests/unhandledRejectionBridge.ts +++ b/apps/ai-game-creator-shell/tests/unhandledRejectionBridge.ts @@ -10,11 +10,31 @@ * 有人接手;没人读说明这条拒绝是意外的,交回安装前的监听器(含 vitest 的失败上报), * 不会被静默吞掉。 * + * 安装前用 `rawListeners` 取原始监听器:`process.once` 注册的包装(`.listener`)在拆卸时 + * 按 `once` 装回去,不会退化成每次拒绝都触发的普通监听器;安装期间新增的监听器仍排在 + * 恢复出来的监听器之前。 + * * 用法:用例内 `const uninstall = installUnhandledRejectionBridge()`,在 `finally` 或 * `afterEach` 里 `uninstall()`。 */ +type RejectionListener = (reason: unknown, promise: Promise) => void; +type RawRejectionListener = RejectionListener & { + listener?: RejectionListener; +}; + +function invokeRejectionListener( + listener: RawRejectionListener, + reason: unknown, + promise: Promise, +) { + // once 包装必须调它包裹的原函数:脱离 emitter 直接调用包装会碰到 this 为空。 + (listener.listener ?? listener)(reason, promise); +} + export function installUnhandledRejectionBridge() { - const originalListeners = process.listeners('unhandledRejection'); + const originalListeners = process.rawListeners( + 'unhandledRejection', + ) as RawRejectionListener[]; process.removeAllListeners('unhandledRejection'); const handleRejection = (reason: unknown, promise: Promise) => { let consumed = false; @@ -28,7 +48,7 @@ export function installUnhandledRejectionBridge() { window.dispatchEvent(event); if (!consumed) { for (const listener of originalListeners) { - listener(reason, promise); + invokeRejectionListener(listener, reason, promise); } } }; @@ -36,7 +56,11 @@ export function installUnhandledRejectionBridge() { return () => { process.removeListener('unhandledRejection', handleRejection); for (const listener of originalListeners) { - process.on('unhandledRejection', listener); + if (listener.listener) { + process.once('unhandledRejection', listener.listener); + } else { + process.on('unhandledRejection', listener); + } } }; }