Skip to main content
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.
options
object
required
Hook callbacks configuration
init
function
Called when a resource is constructed
before
function
Called before resource callback execution
after
function
Called after resource callback execution
destroy
function
Called when resource is destroyed
promiseResolve
function
Called when a Promise is resolved
trackPromises
boolean
default:"true"
Whether to track Promise resources
return
AsyncHook
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.
return
number
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.
return
number
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.
return
object
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.
return
Map
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.
return
AsyncHook
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.
return
AsyncHook
Reference to the AsyncHook instance (chainable)

Hook Callbacks

init(asyncId, type, triggerAsyncId, resource)

Called when a resource is constructed.
asyncId
number
required
Unique ID for the async resource
type
string
required
Type of the async resource (e.g., ‘TCPWRAP’, ‘Timeout’, ‘PROMISE’)
triggerAsyncId
number
required
ID of the resource that caused this resource to be created
resource
object
required
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.
asyncId
number
required
The unique identifier of the resource

after(asyncId)

Called immediately after the callback completes.
asyncId
number
required
The unique identifier of the resource

destroy(asyncId)

Called when the resource is destroyed.
asyncId
number
required
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.
asyncId
number
required
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: