Node.js Async Hooks — Complete Guide to Async Lifecycle Tracking
In this tutorial, you will learn about Node.js Async Hooks. We cover key concepts, practical examples, and best practices to help you master this topic.
Node.js async hooks API tracks the lifecycle of asynchronous resources, providing callbacks for init, before, after, destroy, and promiseResolve events across the entire application.
What You'll Learn
By the end of this tutorial, you'll use async hooks to monitor async operations, implement context propagation (like AsyncLocalStorage), track resource leaks, and build diagnostic tools.
Why Async Hooks Matter
Debugging async code is notoriously difficult. Async hooks give you visibility into every async operation, enabling context-aware logging, request tracing, and resource leak detection.
Real-World Use
A tracing system uses async hooks to assign every async operation a trace ID, propagating it through promises, timers, and I/O callbacks so logs from a single request are correlated.
Async Hooks Path
flowchart LR
A[Async Patterns] --> B[Async Hooks]
B --> C[Error Handling]
C --> D[Debugging]
D --> E[Profiling]
B --> F{You Are Here}
style F fill:#f90,color:#fff
Creating an AsyncHook
The createHook method registers callbacks for async resource lifecycle events.
const asyncHooks = require("node:async_hooks");
const fs = require("node:fs");
const hook = asyncHooks.createHook({
init(asyncId, type, triggerAsyncId) {
fs.writeSync(1, `Init: ${type} id=${asyncId} trigger=${triggerAsyncId}\n`);
},
before(asyncId) {
fs.writeSync(1, `Before: id=${asyncId}\n`);
},
after(asyncId) {
fs.writeSync(1, `After: id=${asyncId}\n`);
},
destroy(asyncId) {
fs.writeSync(1, `Destroy: id=${asyncId}\n`);
},
});
hook.enable();
setTimeout(() => {}, 100);
AsyncLocalStorage Context
AsyncLocalStorage provides context propagation without passing arguments manually through async chains.
const { AsyncLocalStorage } = require("node:async_hooks");
const als = new AsyncLocalStorage();
function log(message) {
const store = als.getStore();
console.log(`[${store?.requestId ?? "unknown"}] ${message}`);
}
app.use((req, res, next) => {
als.run({ requestId: req.id }, () => {
log("Request started");
next();
});
});
Tracking Async Resource Types
Different async operations have distinct types: TIMERWRAP, PROMISE, FSREQCALLBACK, TCPWRAP, etc.
const asyncHooks = require("node:async_hooks");
const types = new Map();
const hook = asyncHooks.createHook({
init(asyncId, type) {
types.set(type, (types.get(type) || 0) + 1);
},
destroy(asyncId, type) {
// tracking cleanup
},
});
hook.enable();
setInterval(() => {
console.log("Active async resources by type:");
types.forEach((count, type) => console.log(` ${type}: ${count}`));
}, 5000);
Enabling and Disabling Hooks
Hooks can be enabled and disabled at runtime. Disable hooks when not needed to avoid performance overhead.
const asyncHooks = require("node:async_hooks");
const hook = asyncHooks.createHook({
init() {},
before() {},
after() {},
destroy() {},
});
hook.enable();
console.log("Hooks enabled, tracking async operations...");
setTimeout(() => {
hook.disable();
console.log("Hooks disabled, no longer tracking");
}, 3000);
Execution Async ID
The executionAsyncId function returns the ID of the currently executing async resource.
const { executionAsyncId, triggerAsyncId } = require("node:async_hooks");
console.log("Current execution async ID:", executionAsyncId());
console.log("Trigger async ID:", triggerAsyncId());
setTimeout(() => {
console.log("Inside timeout - execution ID:", executionAsyncId());
console.log("Inside timeout - trigger ID:", triggerAsyncId());
}, 100);
Common Mistakes
1. Using console.log Inside Async Hook Callbacks
console.log is async and can trigger init hooks recursively. Use fs.writeSync(1, ...) for synchronous output.
2. Performance Impact of Async Hooks
Async hooks add overhead to every async operation. Enable only during debugging or diagnostics.
3. Not Disabling Hooks After Use
Active hooks slow down the application permanently. Disable hooks when the diagnostic session ends.
4. Modifying Global State in Hooks
Async hook callbacks should be side-effect free. Avoid modifying shared state synchronously.
5. Confusing executionAsyncId and triggerAsyncId
executionAsyncId is the current resource. triggerAsyncId is the resource that created the current one.
Practice Questions
1. What are the four main async hook callbacks?
init, before, after, and destroy. promiseResolve is an additional optional callback.
2. What is the difference between executionAsyncId and triggerAsyncId?
executionAsyncId identifies the currently executing resource. triggerAsyncId identifies the resource that created it.
3. Why should you avoid console.log in hook callbacks?
console.log is async and can cause infinite Recursion by triggering init for new async resources.
4. What is AsyncLocalStorage used for?
Propagating context (like request IDs) through async chains without passing it explicitly.
5. Challenge: Build a simple context propagator using async hooks.
const { createHook, executionAsyncId } = require("node:async_hooks");
const contexts = new Map();
const hook = createHook({
init(asyncId, type, triggerAsyncId) {
if (contexts.has(triggerAsyncId)) {
contexts.set(asyncId, contexts.get(triggerAsyncId));
}
},
destroy(asyncId) { contexts.delete(asyncId); },
});
hook.enable();
module.exports = { contexts, executionAsyncId };
FAQ
Mini Project: Request Tracing Middleware
Build Express middleware that assigns unique IDs to every request using AsyncLocalStorage.
const { AsyncLocalStorage } = require("node:async_hooks");
const crypto = require("node:crypto");
const als = new AsyncLocalStorage();
function requestTracker(req, res, next) {
const requestId = crypto.randomUUID();
als.run({ requestId, startTime: Date.now() }, () => {
res.on("finish", () => {
const store = als.getStore();
const duration = Date.now() - store.startTime;
console.log(`[${store.requestId}] ${req.method} ${req.url} ${res.statusCode} ${duration}ms`);
});
next();
});
}
module.exports = { als, requestTracker };
What's Next
Node.js Error Handling Node.js Debugging Node.js Profiling
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro