Frontmatter
| id | 8896 |
| title | Refactor Node.js Unhandled Rejection Handling to process.emit Interception |
| state | Closed |
| labels | testingarchitecturecore |
| assignees | tobiu |
| createdAt | Jan 27, 2026, 4:13 PM |
| updatedAt | Jan 27, 2026, 4:15 PM |
| githubUrl | https://github.com/neomjs/neo/issues/8896 |
| author | tobiu |
| commentsCount | 1 |
| parentIssue | null |
| subIssues | [] |
| subIssuesCompleted | 0 |
| subIssuesTotal | 0 |
| blockedBy | [] |
| blocking | [] |
| closedAt | Jan 27, 2026, 4:15 PM |
Refactor Node.js Unhandled Rejection Handling to process.emit Interception

tobiu
Jan 27, 2026, 4:15 PM
Input from Gemini 3 Pro:
✦ I have refactored the unhandled rejection handling in
src/Neo.mjsto interceptprocess.emitdirectly. This robustly suppressesNeo.isDestroyedrejections across all Node.js environments, including test runners like Playwright that might otherwise preempt or ignore standardprocess.onlisteners.I verified this fix by running the full unit test suite, confirming that all tests pass without unhandled rejection failures.
I also cleaned up
test/playwright/setup.mjsto remove the temporary local patch, ensuring the core framework handles this concern globally.
tobiu assigned to @tobiu on Jan 27, 2026, 4:15 PM
tobiu closed this issue on Jan 27, 2026, 4:15 PM
Context
Neo.mjs components use a "Trap Pattern" (
promiseUpdate,trap,timeout) to reject pending promises withNeo.isDestroyedwhen a component is destroyed. This prevents execution from continuing on dead objects. However, in Node.js environments (like Playwright unit tests or Server-Side Generation), these rejections often bubble up as "Unhandled Rejections," causing test runners to fail or processes to crash.Problem
The existing mechanism in
src/Neo.mjsusedprocess.on('unhandledRejection')to suppress these specific errors. However, this approach is fragile because test runners (like Playwright) attach their own listeners that may execute before the suppression logic or simply count the event emission itself as a failure.Solution
Refactor
src/Neo.mjsto intercept theunhandledRejectionevent at the source by patchingprocess.emit.Implementation
process.emit: Wrap the native emit function.'unhandledRejection'AND the reason isNeo.isDestroyed, returntrueimmediately. This swallows the event, preventing it from reaching any listeners.process.emit.Intent
This change transforms
Neo.isDestroyedfrom an "error to be caught" into a "control flow signal to be ignored" at the infrastructure level. It ensures robustness across all Node.js-based environments (Tests, Scripts, SSG) without requiring repetitivetry/catchblocks in application or test code.