Understanding Node.js AsyncHooks and AsyncLocalStorage for Asynchronous Resource Tracking
This article explains Node.js async_hooks and AsyncLocalStorage APIs, describing asynchronous resource concepts, lifecycle hooks, usage scenarios, performance impact, and practical code examples for tracing async call chains, custom async resources, and integrating context propagation in backend applications.
What Are Asynchronous Resources
In Node.js an asynchronous resource is any object that has an associated callback which may be invoked one or more times (e.g., fs.open creates an FSReqCallback , net.createServer creates a TCP object). AsyncHooks abstracts these resources without caring about their specific type.
Why Track Asynchronous Resources
Because Node.js uses an event‑loop based non‑blocking I/O model, the original caller of an asynchronous operation is lost by the time its callback runs, making it hard to reconstruct call chains.
Scenario 1
const fs = require('fs');
function callback(err, data) {
console.log('callback', data);
}
fs.readFile('a.txt', callback);
console.log('after a');
fs.readFile('b.txt', callback);
console.log('after b');
// output: after a, after b, callback undefined, callback undefinedThe logs show that we cannot tell which callback belongs to which readFile call.
Scenario 2
function main() {
setTimeout(() => {
throw Error(1);
}, 0);
}
main();
// Error stack is incomplete because the async boundary is lost.Both examples illustrate the need for a mechanism that preserves the async execution context.
AsyncHooks Overview
AsyncHooks provides lifecycle callbacks ( init , before , after , destroy , promiseResolve ) that are triggered for every asynchronous resource.
const async_hooks = require('async_hooks');
const asyncHook = async_hooks.createHook({ init, before, after, destroy, promiseResolve });
asyncHook.enable();Hook Callback Parameters
asyncId : unique identifier of the resource (starts at 1).
type : string describing the resource type (e.g., FSREQCALLBACK , TCPWRAP ).
triggerAsyncId : asyncId of the resource that created the current one.
resource : the actual object representing the async resource.
Performance Impact
Enabling all hooks adds noticeable overhead. Benchmarks on Node v14.16.0 show a drop from ~11 734 req/s (regular) to ~6 250 req/s (full hooks) for a typical HTTP server.
AsyncResource
Custom asynchronous resources can be created by extending AsyncResource . This allows manual control over the async context.
const { AsyncResource } = require('async_hooks');
class MyResource extends AsyncResource {
constructor() { super('my-resource'); }
close() { this.emitDestroy(); }
}AsyncLocalStorage
AsyncLocalStorage provides a simple API to store data throughout an async call chain, similar to thread‑local storage in other languages.
const http = require('http');
const { AsyncLocalStorage } = require('async_hooks');
const asyncLocalStorage = new AsyncLocalStorage();
let idSeq = 0;
http.createServer((req, res) => {
asyncLocalStorage.run(idSeq++, () => {
console.log('start', asyncLocalStorage.getStore());
setImmediate(() => {
console.log('finish', asyncLocalStorage.getStore());
res.end();
});
});
}).listen(8080);Implementation Details
Internally AsyncLocalStorage registers a tiny async hook that propagates a hidden store from the current resource to any newly created child resource, allowing getStore() to retrieve the value at any point.
Use Cases
Continuation‑local storage (CLS) implementations such as cls‑hooked and asynchronous‑local‑storage .
Clinic.js’s Bubbleprof uses AsyncHooks + Error.captureStackTrace to visualise async call graphs.
Frameworks like Midway and Farrow leverage AsyncLocalStorage to share request‑scoped context without passing explicit parameters.
References
Node.js official async_hooks documentation.
bmeurer/async-hooks-performance-impact (GitHub).
Making async_hooks fast (enough) – internal design notes.
Various blog posts and issue discussions linked in the original article.
ByteFE
Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.