unhandledrejection桥接保留once语义

- 用 rawListeners 记录原始监听器,拆卸时把 once 包装按 process.once 装回,普通监听器仍按 process.on 装回
- 未被 window 消费时调用 once 包装包裹的原函数,避免脱离 emitter 直调包装
- 注释记录安装期间新增监听器排在恢复监听器之前的既有边界
This commit is contained in:
2026-10-02 11:35:13 +08:00
parent da4645f55e
commit 7528ba0116
@@ -10,11 +10,31 @@
* 有人接手;没人读说明这条拒绝是意外的,交回安装前的监听器(含 vitest 的失败上报),
* 不会被静默吞掉。
*
* 安装前用 `rawListeners` 取原始监听器:`process.once` 注册的包装(`.listener`)在拆卸时
* 按 `once` 装回去,不会退化成每次拒绝都触发的普通监听器;安装期间新增的监听器仍排在
* 恢复出来的监听器之前。
*
* 用法:用例内 `const uninstall = installUnhandledRejectionBridge()`,在 `finally` 或
* `afterEach` 里 `uninstall()`。
*/
type RejectionListener = (reason: unknown, promise: Promise<unknown>) => void;
type RawRejectionListener = RejectionListener & {
listener?: RejectionListener;
};
function invokeRejectionListener(
listener: RawRejectionListener,
reason: unknown,
promise: Promise<unknown>,
) {
// 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<unknown>) => {
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);
}
}
};
}