Monitoring pending async operations in Node.js promised environment
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.
Introduction
In Node.js applications, unresolved promises and pending async operations can cause memory leaks, unresponsive servers, and silent failures. Monitoring these operations helps identify resource leaks, performance bottlenecks, and unhandled rejections. Node.js provides built-in tools and patterns for tracking async activity.
Why Monitor Async Operations?
- Resource Leaks: Identifying resource leaks caused by unhandled rejections or unresolved promises that hold references to database connections, file handles, or network sockets.
- Performance Bottlenecks: Spotting operations that do not resolve in expected time frames, blocking the event loop or the execution of other async operations.
- Complexity in Chains: Managing chains of promises and ensuring proper error handling for all possible outcomes.
Method 1: async_hooks (Built-in)
Node.js async_hooks module tracks the lifecycle of all async resources:
Method 2: Promise Wrapper with Timeout
Wrap promises to detect ones that never resolve:
Generic Promise Tracker
Method 3: Unhandled Rejection Detection
Catch promises that reject without a handler:
In Node.js 15+, unhandled rejections terminate the process by default. Configure with:
Method 4: Event Loop Monitoring
Detect when the event loop is blocked by long-running sync operations:
Or use the built-in perf_hooks:
Method 5: wtfnode (Debug Tool)
The wtfnode package shows what is keeping Node.js from exiting:
Health Check Endpoint
Expose async operation status via HTTP:
Common Pitfalls
- async_hooks performance:
async_hooksadds overhead to every async operation. Use it for debugging and development, not production monitoring at scale. - Missing finally/catch: Forgetting
.catch()on a promise creates an unhandled rejection. UsePromise.allSettled()to handle mixed success/failure batches. - Timer leaks:
setIntervalandsetTimeoutkeep the Node.js process alive. Call.unref()on monitoring timers so they do not prevent graceful shutdown. - Connection pool exhaustion: Unresolved database queries hold connection pool slots. Set query timeouts to prevent pool starvation.
- Graceful shutdown: On
SIGTERM, wait for pending operations to complete with a timeout:Promise.race([pendingWork, timeout(5000)]).
Summary
- Use
async_hooksto track the lifecycle of all async resources in development - Wrap promises with timeout tracking to detect operations that never resolve
- Handle
unhandledRejectionglobally to catch missed.catch()calls - Monitor event loop delay with
perf_hooks.monitorEventLoopDelay() - Use
wtfnodeto debug what keeps a Node.js process from exiting
Related reading
- Monitoring UI for Apache kafka - kafka manager vs kafka monitor
- Most useful NLog configurations
- Mount docker host volume but overwrite with container's contents
- Mounting multiple volumes on a docker container?
- Mono class in Java what is, and when to use?
- MPI Large Data all to all transfer
- monk vs mongoose for Mongodb
- Most efficient way to create a zero filled JavaScript array?

System Design Fundamentals
Build a strong foundation in designing scalable, reliable distributed systems.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.