Course contentsShow
← Node.js
Lesson 45 of 1,9801. Node.js Foundations and Runtime InternalsFree lesson

Monitoring Asynchronous Resource Life-cycles

Use the AsyncLocalStorage API to propagate context across asynchronous calls.

Tracking Context Across Async Boundaries

In this lesson, you will learn how to maintain state across asynchronous operations using the AsyncLocalStorage API.

When you write asynchronous code in Node.js, the execution flow often jumps between callbacks, promises, and event loop phases. If you need to keep track of a "context"—such as a unique request ID, a database transaction, or a logged-in user—passing that data manually through every single function argument becomes messy and error-prone. This is known as "prop-drilling."

AsyncLocalStorage solves this by acting as a "storage container" that follows the logical flow of your code, even when it pauses for I/O and resumes later. It ties your data to the asynchronous execution lifecycle rather than the call stack.

How it works

Think of AsyncLocalStorage as a invisible pocket you carry around. You can put something in the pocket at the start of a request, and anywhere within that specific execution chain, you can reach in and retrieve it without having to pass it as a parameter.

const { AsyncLocalStorage } = require('async_hooks');

const storage = new AsyncLocalStorage();

function logWithId(message) {
  const id = storage.getStore();
  console.log(`[Request ${id}] ${message}`);
}

// Start a context
storage.run('123', () => {
  logWithId('Starting database query...');
  
  setTimeout(() => {
    // The ID "123" persists even inside this async callback
    logWithId('Query finished.');
  }, 1000);
});

When you call storage.run(data, callback), Node.js ensures that the provided data is associated with the callback and any subsequent asynchronous operations spawned by it. When the setTimeout eventually fires during the event loop's "Timers" phase, the runtime is smart enough to "re-attach" that specific context, allowing you to access the original ID.

This is a powerful way to implement cross-cutting concerns like logging, tracing, or security authorization without polluting your function signatures with boilerplate data.

Key Takeaway: AsyncLocalStorage allows you to store and access context data across asynchronous operations by linking the data to the execution lifecycle instead of the physical call stack.