What is AsyncLocalStorage in Node.js and How to Use It
This article explains the purpose of the
AsyncLocalStorage class in Node.js and demonstrates how it
propagates transactional context across asynchronous execution paths.
You will learn how this API acts as the Node.js equivalent to
thread-local storage, allowing you to track request-specific data—such
as correlation IDs, user sessions, or database transactions—throughout
the lifecycle of an asynchronous operation without relying on prop
drilling.
The Purpose of AsyncLocalStorage
In multi-threaded environments, developers use Thread-Local Storage (TLS) to store data specific to the current thread of execution. However, Node.js runs on a single-threaded event loop, using callbacks, promises, and async/await to handle concurrency. Because multiple concurrent operations share the same thread, traditional TLS cannot be used to keep track of state for individual requests.
Historically, passing context (like a user ID or transaction token)
meant passing arguments through every function call in the execution
chain—a tedious process known as “prop drilling.”
AsyncLocalStorage (part of the built-in
node:async_hooks module) solves this problem. It provides a
way to store and retrieve state that is bound to the lifetime of a
specific asynchronous control flow, making the data accessible to any
function called within that flow.
How AsyncLocalStorage Propagates Context
AsyncLocalStorage works by tapping into the internal
asynchronous tracking mechanisms of Node.js. When an asynchronous
operation is initiated, Node.js links the parent asynchronous context
with the child asynchronous context.
The lifecycle of propagating context with
AsyncLocalStorage involves three main steps:
- Instantiation: You create an instance of
AsyncLocalStorage. - Context Binding (
run): You use therun()method to initialize the store with a specific value (the context) and execute a callback function. All code executed within this callback—including nested asynchronous operations like database queries, HTTP requests, or timers—will have access to this store. - Retrieval (
getStore): Any function executed within the asynchronous boundary can call thegetStore()method to retrieve the context.
Code Example: Request Tracking
Below is a practical example demonstrating how
AsyncLocalStorage propagates a correlation ID across
multiple asynchronous boundaries without passing it as an argument.
import { AsyncLocalStorage } from 'node:async_hooks';
import { randomUUID } from 'node:crypto';
// 1. Instantiate the storage
const asyncLocalStorage = new AsyncLocalStorage();
// Simulated database operation
function saveToDatabase() {
// 3. Retrieve the context from anywhere in the call chain
const context = asyncLocalStorage.getStore();
console.log(`[DB] Saving data for Request ID: ${context?.requestId}`);
}
// Simulated service layer
async function processBusinessLogic() {
console.log('[Service] Processing business logic...');
// Simulating an asynchronous delay (e.g., API call)
await new Promise((resolve) => setTimeout(resolve, 100));
saveToDatabase();
}
// Simulated HTTP server request handler
function handleRequest(user) {
const requestId = randomUUID();
const context = { requestId, user };
// 2. Run the asynchronous chain inside the context
asyncLocalStorage.run(context, async () => {
console.log(`[Server] Received request. Assigned ID: ${requestId}`);
await processBusinessLogic();
});
}
// Trigger two concurrent requests
handleRequest('Alice');
handleRequest('Bob');Explanation of Propagation in the Example
When handleRequest('Alice') is called, a unique request
ID is generated. The asyncLocalStorage.run method binds
this ID to the execution flow.
Even though processBusinessLogic and
saveToDatabase do not accept the request ID as an argument,
and even though they run asynchronously after a setTimeout
delay, Node.js internally propagates the context. When
getStore() is called inside saveToDatabase(),
Node.js resolves the asynchronous execution path back to the execution
context of the specific request, returning the correct ID for both Alice
and Bob concurrently.