Run the ticks and microtasks that 'unhandledRejection' listeners queue

DY
12d ago
+236-30

What does this PR do?

What an unhandledRejection listener queues with await, process.nextTick or queueMicrotask now runs even when nothing else is pending. Bun ran it at the next turn of the event loop, so with no next turn it was dropped and the process exited.

process.on("unhandledRejection", async e => {
  console.log("listener");
  await null;
  console.log("after await"); // Node: printed. Bun: never
});
Promise.reject(new Error("x"));

As in Node's processTicksAndRejections, the listeners of every pending rejection run first, then one microtask checkpoint, then the list is read again. handleRejectedPromises does the checkpoint at the end of a batch, and only if a listener took a rejection: expect(fn).toThrow() reports rejections with test code still on the stack.

nothing else pendingmainthis PR and Node
async listenerstops at its first awaitruns to the end
two rejections, each listener queues a tick and a microtaskboth droppedlistener a, listener b, ticks, microtasks
a listener's tick rejects another promisenever reportedreported

captureRejections

test-event-capture-rejections.js passed only because it stopped early: its third stage continues from a process.nextTick in an unhandledRejection listener, so it exited 0 after 3 of its 13 stages. It now runs all 13, which needed these in node:events:

mainthis PR and Node
EventEmitter.captureRejections = true, emitter whose constructor never ranunhandled rejectioncaptured
a listener returns a thenable that is not a promiseignoredcaptured
reading then throwsignored'error' is emitted
an async 'error' listener rejectsunhandled rejectioncaptured once

The setter swaps EventEmitter.prototype.emit between the two built-in variants, so emit without capture still pays nothing. It leaves a userland prototype.emit in place.

One difference from Node is left as on main: when a listener throws, Bun still calls the listeners of the other pending rejections.

How did you verify your code works?

New tests in process.test.js and event-emitter.test.ts, with every expectation checked against Node 26.7. They pass with bun bd test; with USE_SYSTEM_BUN=1 eight fail and the rest are controls that were already right. Undoing the capture check in addCatch or the userland emit guard each fails a test. 15 more captureRejections scenarios print the same as Node, and the 17 vendored Node tests that mention either feature pass.