Node.js › Event Loop & Non-blocking IO

How would you use async_hooks (the lower-level predecessor to AsyncLocalStorage) to trace the lifecycle of asynchronous resources in Node.js?

Advanced

Answer

async_hooks provides low-level lifecycle callbacks (init, before, after, destroy) fired whenever an asynchronous resource (a Promise, a Timeout, a TCP connection) is created and as it progresses through its lifecycle, letting you build tools that track the causal relationship between async operations (which operation triggered which) -- this is powerful but intricate to use correctly, which is why AsyncLocalStorage (built on top of async_hooks) is recommended for the common context-propagation use case, with raw async_hooks reserved for building specialized diagnostic or APM tooling.
const asyncHooks = require('node:async_hooks');

const hook = asyncHooks.createHook({
  init(asyncId, type, triggerAsyncId) {
    fs.writeSync(1, `Init: ${type}(${asyncId}) triggered by ${triggerAsyncId}\n`);
  },
});
hook.enable();
Real-world example An APM vendor's Node.js instrumentation library uses async_hooks internally to build a complete causal graph of every asynchronous operation within a request, powering the distributed tracing feature that shows exactly which downstream calls a given request triggered and in what order.

Common follow-ups: Why is AsyncLocalStorage recommended over raw async_hooks for the common context-propagation use case?;What performance overhead does enabling async_hooks introduce, and why does that matter for production use?