The Async Hooks API is experimental and has known usability issues and performance implications.
For async context tracking, use the stable AsyncLocalStorage API instead.
Overview
The node:async_hooks module provides an API to track asynchronous resources and their lifecycle events in Node.js applications.
import async_hooks from 'node:async_hooks';
// or
const async_hooks = require('node:async_hooks');
Core Concepts
An asynchronous resource represents an object with an associated callback. This could be:
- Network connections (TCP, HTTP)
- File system operations
- Timers and immediates
- Promises
- Any object with async callbacks
Execution IDs
Each async resource gets a unique asyncId. You can track:
- Current execution context via
executionAsyncId()
- Trigger context via
triggerAsyncId() - what caused this resource to be created
Functions
createHook(options)
Creates a new AsyncHook instance for tracking async resource lifecycle.
Hook callbacks configurationCalled when a resource is constructed
Called before resource callback execution
Called after resource callback execution
Called when resource is destroyed
Called when a Promise is resolved
Whether to track Promise resources
AsyncHook instance for controlling the hooks
import { createHook } from 'node:async_hooks';
const asyncHook = createHook({
init(asyncId, type, triggerAsyncId, resource) {
console.log(`Resource ${type}(${asyncId}) created`);
},
before(asyncId) {
console.log(`Before callback: ${asyncId}`);
},
after(asyncId) {
console.log(`After callback: ${asyncId}`);
},
destroy(asyncId) {
console.log(`Resource ${asyncId} destroyed`);
}
});
asyncHook.enable();
executionAsyncId()
Returns the asyncId of the current execution context.
The asyncId of the current execution context
import { executionAsyncId } from 'node:async_hooks';
import fs from 'node:fs';
console.log(executionAsyncId()); // 1 - bootstrap
fs.open('.', 'r', (err, fd) => {
console.log(executionAsyncId()); // 6 - open()
});
triggerAsyncId()
Returns the ID of the resource responsible for calling the current callback.
The asyncId that triggered the current execution
import { createHook, triggerAsyncId } from 'node:async_hooks';
import net from 'node:net';
const server = net.createServer((conn) => {
// The resource that triggered this callback
// is the new connection
console.log(triggerAsyncId());
}).listen(port);
executionAsyncResource()
Returns the resource representing the current execution context.
The resource object for the current execution
import { open } from 'node:fs';
import { executionAsyncId, executionAsyncResource } from 'node:async_hooks';
console.log(executionAsyncId(), executionAsyncResource()); // 1 {}
open(new URL(import.meta.url), 'r', (err, fd) => {
console.log(executionAsyncId(), executionAsyncResource()); // 7 FSReqWrap
});
asyncWrapProviders
Returns a map of provider types to their numeric IDs.
Map containing all async resource types
AsyncHook Class
The AsyncHook class provides methods to control hook execution.
enable()
Enables the callbacks for this AsyncHook instance.
Reference to the AsyncHook instance (chainable)
import { createHook } from 'node:async_hooks';
const hook = createHook(callbacks).enable();
disable()
Disables the callbacks for this AsyncHook instance.
Reference to the AsyncHook instance (chainable)
Hook Callbacks
init(asyncId, type, triggerAsyncId, resource)
Called when a resource is constructed.
Unique ID for the async resource
Type of the async resource (e.g., ‘TCPWRAP’, ‘Timeout’, ‘PROMISE’)
ID of the resource that caused this resource to be created
Reference to the actual async resource
import { createHook, executionAsyncId } from 'node:async_hooks';
import net from 'node:net';
import fs from 'node:fs';
createHook({
init(asyncId, type, triggerAsyncId) {
const eid = executionAsyncId();
fs.writeSync(
1,
`${type}(${asyncId}): trigger: ${triggerAsyncId} execution: ${eid}\n`
);
},
}).enable();
net.createServer((conn) => {}).listen(8080);
before(asyncId)
Called just before the resource’s callback is executed.
The unique identifier of the resource
after(asyncId)
Called immediately after the callback completes.
The unique identifier of the resource
destroy(asyncId)
Called when the resource is destroyed.
The unique identifier of the resource
Using the destroy hook enables tracking of Promise instances via garbage collection, which adds overhead.
promiseResolve(asyncId)
Called when the resolve function passed to the Promise constructor is invoked.
The unique identifier of the Promise resource
import { createHook } from 'node:async_hooks';
createHook({
promiseResolve(asyncId) {
console.log(`Promise ${asyncId} resolved`);
}
}).enable();
new Promise((resolve) => resolve(true)).then((a) => {});
// Logs: Promise 5 resolved
Promise Execution Tracking
By default, promises don’t get asyncIds due to performance costs. Installing an async hook enables promise tracking:
import { createHook, executionAsyncId, triggerAsyncId } from 'node:async_hooks';
// Without hooks
Promise.resolve(1729).then(() => {
console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`);
});
// produces: eid 1 tid 0
// With hooks enabled
createHook({ init() {} }).enable();
Promise.resolve(1729).then(() => {
console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`);
});
// produces: eid 7 tid 6
Disabling Promise Tracking
To opt out of promise tracking for better performance:
import { createHook } from 'node:async_hooks';
createHook({
init(asyncId, type) {
// This won't be called for promises
},
trackPromises: false,
}).enable();
Complete Example
Tracking async context through nested operations:
import async_hooks from 'node:async_hooks';
import fs from 'node:fs';
import net from 'node:net';
let indent = 0;
async_hooks.createHook({
init(asyncId, type, triggerAsyncId) {
const eid = async_hooks.executionAsyncId();
const indentStr = ' '.repeat(indent);
fs.writeSync(
1,
`${indentStr}${type}(${asyncId}): trigger: ${triggerAsyncId} execution: ${eid}\n`
);
},
before(asyncId) {
const indentStr = ' '.repeat(indent);
fs.writeSync(1, `${indentStr}before: ${asyncId}\n`);
indent += 2;
},
after(asyncId) {
indent -= 2;
const indentStr = ' '.repeat(indent);
fs.writeSync(1, `${indentStr}after: ${asyncId}\n`);
},
destroy(asyncId) {
const indentStr = ' '.repeat(indent);
fs.writeSync(1, `${indentStr}destroy: ${asyncId}\n`);
},
}).enable();
net.createServer(() => {}).listen(8080, () => {
setTimeout(() => {
console.log('>>>', async_hooks.executionAsyncId());
}, 10);
});
Important Considerations
Printing in AsyncHook CallbacksNever use console.log() inside AsyncHook callbacks as it causes infinite recursion. Use synchronous operations like fs.writeFileSync() instead.
import { writeFileSync } from 'node:fs';
import { format } from 'node:util';
function debug(...args) {
// Safe for use in AsyncHook callbacks
writeFileSync('log.out', `${format(...args)}\n`, { flag: 'a' });
}
Error HandlingIf any AsyncHook callback throws an error, the application will print the stack trace and exit. All uncaughtException listeners are removed, forcing process termination.
Migration Path
Consider migrating to these stable alternatives: