Skip to content

Асинхронные хуки

[Стабильность: 1 - Экспериментальный]

Стабильность: 1 Стабильность: 1 - Экспериментальный. Пожалуйста, откажитесь от этого API, если можете. Мы не рекомендуем использовать API createHook, AsyncHook и executionAsyncResource, поскольку у них есть проблемы с удобством использования, риски безопасности и влияние на производительность. Случаи использования отслеживания асинхронного контекста лучше обслуживаются стабильным API AsyncLocalStorage. Если у вас есть случай использования createHook, AsyncHook или executionAsyncResource за рамками отслеживания контекста, решаемого AsyncLocalStorage или диагностических данных, которые в настоящее время предоставляются каналом диагностики, пожалуйста, откройте проблему по адресу https://github.com/nodejs/node/issues, описав свой случай использования, чтобы мы могли создать более целенаправленный API.

Исходный код: lib/async_hooks.js

Мы настоятельно не рекомендуем использовать API async_hooks. Другие API, которые могут охватить большинство его вариантов использования, включают:

Модуль node:async_hooks предоставляет API для отслеживания асинхронных ресурсов. К нему можно получить доступ, используя:

js
import async_hooks from 'node:async_hooks'
js
const async_hooks = require('node:async_hooks')

Терминология

Асинхронный ресурс представляет собой объект со связанной обратной связью. Эта обратная связь может вызываться несколько раз, например, событие 'connection' в net.createServer(), или только один раз, как в fs.open(). Ресурс также может быть закрыт до вызова обратной связи. AsyncHook не делает явного различия между этими различными случаями, но будет представлять их как абстрактную концепцию, которая является ресурсом.

Если используются Worker, каждый поток имеет независимый интерфейс async_hooks, и каждый поток будет использовать новый набор асинхронных идентификаторов.

Обзор

Ниже представлен краткий обзор публичного API.

js
import async_hooks from 'node:async_hooks'

// Возвращает идентификатор текущего контекста выполнения.
const eid = async_hooks.executionAsyncId()

// Возвращает идентификатор обработчика, ответственного за вызов обратного вызова
// текущей области выполнения.
const tid = async_hooks.triggerAsyncId()

// Создает новый экземпляр AsyncHook. Все эти обратные вызовы являются необязательными.
const asyncHook = async_hooks.createHook({ init, before, after, destroy, promiseResolve })

// Разрешает вызывать обратные вызовы этого экземпляра AsyncHook. Это не является неявным
// действием после запуска конструктора, и его необходимо явно запустить, чтобы начать
// выполнение обратных вызовов.
asyncHook.enable()

// Отключает прослушивание новых асинхронных событий.
asyncHook.disable()

//
// Ниже приведены обратные вызовы, которые могут быть переданы в createHook().
//

// init() вызывается во время конструирования объекта. Ресурс может не завершить
// конструирование, когда выполняется этот обратный вызов. Следовательно, все поля
// ресурса, на который ссылается "asyncId", могут быть еще не заполнены.
function init(asyncId, type, triggerAsyncId, resource) {}

// before() вызывается непосредственно перед вызовом обратного вызова ресурса. Он может быть
// вызван 0-N раз для обработчиков (таких как TCPWrap), и будет вызван ровно 1
// раз для запросов (таких как FSReqCallback).
function before(asyncId) {}

// after() вызывается сразу после завершения обратного вызова ресурса.
function after(asyncId) {}

// destroy() вызывается при уничтожении ресурса.
function destroy(asyncId) {}

// promiseResolve() вызывается только для ресурсов promise, когда
// вызывается функция resolve(), переданная в конструктор Promise
// (непосредственно или через другие способы разрешения promise).
function promiseResolve(asyncId) {}
js
const async_hooks = require('node:async_hooks')

// Возвращает идентификатор текущего контекста выполнения.
const eid = async_hooks.executionAsyncId()

// Возвращает идентификатор обработчика, ответственного за вызов обратного вызова
// текущей области выполнения.
const tid = async_hooks.triggerAsyncId()

// Создает новый экземпляр AsyncHook. Все эти обратные вызовы являются необязательными.
const asyncHook = async_hooks.createHook({ init, before, after, destroy, promiseResolve })

// Разрешает вызывать обратные вызовы этого экземпляра AsyncHook. Это не является неявным
// действием после запуска конструктора, и его необходимо явно запустить, чтобы начать
// выполнение обратных вызовов.
asyncHook.enable()

// Отключает прослушивание новых асинхронных событий.
asyncHook.disable()

//
// Ниже приведены обратные вызовы, которые могут быть переданы в createHook().
//

// init() вызывается во время конструирования объекта. Ресурс может не завершить
// конструирование, когда выполняется этот обратный вызов. Следовательно, все поля
// ресурса, на который ссылается "asyncId", могут быть еще не заполнены.
function init(asyncId, type, triggerAsyncId, resource) {}

// before() вызывается непосредственно перед вызовом обратного вызова ресурса. Он может быть
// вызван 0-N раз для обработчиков (таких как TCPWrap), и будет вызван ровно 1
// раз для запросов (таких как FSReqCallback).
function before(asyncId) {}

// after() вызывается сразу после завершения обратного вызова ресурса.
function after(asyncId) {}

// destroy() вызывается при уничтожении ресурса.
function destroy(asyncId) {}

// promiseResolve() вызывается только для ресурсов promise, когда
// вызывается функция resolve(), переданная в конструктор Promise
// (непосредственно или через другие способы разрешения promise).
function promiseResolve(asyncId) {}

async_hooks.createHook(callbacks)

Добавлено в: v8.1.0

Регистрирует функции для вызова для различных событий жизненного цикла каждой асинхронной операции.

Колбеки init()/before()/after()/destroy() вызываются для соответствующих асинхронных событий в течение жизненного цикла ресурса.

Все колбеки необязательны. Например, если нужно отслеживать только очистку ресурса, то нужно передать только колбек destroy. Подробности обо всех функциях, которые могут быть переданы в callbacks, находятся в разделе Хук-колбеки.

js
import { createHook } from 'node:async_hooks'

const asyncHook = createHook({
  init(asyncId, type, triggerAsyncId, resource) {},
  destroy(asyncId) {},
})
js
const async_hooks = require('node:async_hooks')

const asyncHook = async_hooks.createHook({
  init(asyncId, type, triggerAsyncId, resource) {},
  destroy(asyncId) {},
})

Колбеки будут унаследованы через цепочку прототипов:

js
class MyAsyncCallbacks {
  init(asyncId, type, triggerAsyncId, resource) {}
  destroy(asyncId) {}
}

class MyAddedCallbacks extends MyAsyncCallbacks {
  before(asyncId) {}
  after(asyncId) {}
}

const asyncHook = async_hooks.createHook(new MyAddedCallbacks())

Поскольку промисы являются асинхронными ресурсами, жизненный цикл которых отслеживается через механизм асинхронных хуков, колбеки init(), before(), after() и destroy() не должны быть асинхронными функциями, возвращающими промисы.

Обработка ошибок

Если какие-либо обратные вызовы AsyncHook вызывают исключение, приложение выведет трассировку стека и завершит работу. Путь завершения работы следует пути необработанного исключения, но все слушатели 'uncaughtException' удаляются, что приводит к принудительному завершению процесса. Обратные вызовы 'exit' все равно будут вызываться, если приложение не запущено с флагом --abort-on-uncaught-exception, в этом случае будет выведена трассировка стека, приложение завершится и останется core-файл.

Причина такого поведения при обработке ошибок заключается в том, что эти обратные вызовы выполняются в потенциально нестабильных точках жизненного цикла объекта, например, во время создания и уничтожения класса. Из-за этого считается необходимым быстро завершить процесс, чтобы предотвратить непреднамеренный сбой в будущем. В будущем это может измениться, если будет проведен всесторонний анализ, чтобы гарантировать, что исключение может следовать нормальному потоку управления без непреднамеренных побочных эффектов.

Вывод в обратных вызовах AsyncHook

Поскольку вывод в консоль является асинхронной операцией, console.log() вызовет обратные вызовы AsyncHook. Использование console.log() или аналогичных асинхронных операций внутри функции обратного вызова AsyncHook приведет к бесконечной рекурсии. Простое решение для этого при отладке — использовать синхронную операцию логирования, такую как fs.writeFileSync(file, msg, flag). Это выведет сообщение в файл и не вызовет AsyncHook рекурсивно, поскольку это синхронная операция.

js
import { writeFileSync } from 'node:fs'
import { format } from 'node:util'

function debug(...args) {
  // Используйте подобную функцию при отладке внутри обратного вызова AsyncHook
  writeFileSync('log.out', `${format(...args)}\n`, { flag: 'a' })
}
js
const fs = require('node:fs')
const util = require('node:util')

function debug(...args) {
  // Используйте подобную функцию при отладке внутри обратного вызова AsyncHook
  fs.writeFileSync('log.out', `${util.format(...args)}\n`, { flag: 'a' })
}

Если для логирования требуется асинхронная операция, можно отслеживать, что вызвало асинхронную операцию, используя информацию, предоставленную самим AsyncHook. В этом случае логирование следует пропустить, если именно оно вызвало обратный вызов AsyncHook. Таким образом, в противном случае бесконечная рекурсия прерывается.

Класс: AsyncHook

Класс AsyncHook предоставляет интерфейс для отслеживания событий жизненного цикла асинхронных операций.

asyncHook.enable()

  • Возвращает: <AsyncHook> Ссылка на asyncHook.

Включает обратные вызовы для заданного экземпляра AsyncHook. Если обратные вызовы не предоставлены, включение не выполняется.

Экземпляр AsyncHook по умолчанию отключен. Если экземпляр AsyncHook должен быть включен сразу после создания, можно использовать следующий шаблон.

js
import { createHook } from 'node:async_hooks'

const hook = createHook(callbacks).enable()
js
const async_hooks = require('node:async_hooks')

const hook = async_hooks.createHook(callbacks).enable()

asyncHook.disable()

  • Возвращает: <AsyncHook> Ссылка на asyncHook.

Отключает обратные вызовы для заданного экземпляра AsyncHook из глобального пула обратных вызовов AsyncHook, которые должны быть выполнены. После того, как хук отключен, он не будет вызываться снова до тех пор, пока не будет включен.

Для согласованности API disable() также возвращает экземпляр AsyncHook.

Обратные вызовы хуков

Ключевые события в жизненном цикле асинхронных событий были разделены на четыре области: создание экземпляра, до/после вызова обратного вызова и когда экземпляр уничтожается.

init(asyncId, type, triggerAsyncId, resource)

  • asyncId <number> Уникальный ID для асинхронного ресурса.
  • type <string> Тип асинхронного ресурса.
  • triggerAsyncId <number> Уникальный ID асинхронного ресурса, в контексте выполнения которого был создан этот асинхронный ресурс.
  • resource <Object> Ссылка на ресурс, представляющий асинхронную операцию, которая должна быть освобождена во время destroy.

Вызывается, когда создается класс, который имеет возможность испустить асинхронное событие. Это не означает, что экземпляр должен вызвать before/after до вызова destroy, только то, что такая возможность существует.

Это поведение можно наблюдать, сделав что-то вроде открытия ресурса, а затем закрытия его до того, как ресурс может быть использован. Следующий фрагмент демонстрирует это.

js
import { createServer } from 'node:net'

createServer().listen(function () {
  this.close()
})
// ИЛИ
clearTimeout(setTimeout(() => {}, 10))
js
require('node:net')
  .createServer()
  .listen(function () {
    this.close()
  })
// ИЛИ
clearTimeout(setTimeout(() => {}, 10))

Каждому новому ресурсу назначается ID, который является уникальным в рамках текущего экземпляра Node.js.

type

type — это строка, идентифицирующая тип ресурса, который вызвал init. Обычно она соответствует имени конструктора ресурса.

type ресурсов, созданных самим Node.js, может меняться в любом выпуске Node.js. Допустимые значения включают TLSWRAP, TCPWRAP, TCPSERVERWRAP, GETADDRINFOREQWRAP, FSREQCALLBACK, Microtask и Timeout. Проверьте исходный код используемой версии Node.js, чтобы получить полный список.

Кроме того, пользователи AsyncResource создают асинхронные ресурсы независимо от самого Node.js.

Существует также тип ресурса PROMISE, который используется для отслеживания экземпляров Promise и асинхронной работы, запланированной ими.

Пользователи могут определять свой собственный type при использовании открытого API для встраивания.

Возможны коллизии имен типов. Разработчикам рекомендуется использовать уникальные префиксы, такие как имя пакета npm, для предотвращения коллизий при прослушивании хуков.

triggerAsyncId

triggerAsyncId — это asyncId ресурса, который вызвал (или "запустил") инициализацию нового ресурса и вызвал init. Это отличается от async_hooks.executionAsyncId(), который показывает только когда был создан ресурс, тогда как triggerAsyncId показывает, почему был создан ресурс.

Ниже приведена простая демонстрация triggerAsyncId:

js
import { createHook, executionAsyncId } from 'node:async_hooks'
import { stdout } from 'node:process'
import net from 'node:net'
import fs from 'node:fs'

createHook({
  init(asyncId, type, triggerAsyncId) {
    const eid = executionAsyncId()
    fs.writeSync(stdout.fd, `${type}(${asyncId}): trigger: ${triggerAsyncId} execution: ${eid}\n`)
  },
}).enable()

net.createServer(conn => {}).listen(8080)
js
const { createHook, executionAsyncId } = require('node:async_hooks')
const { stdout } = require('node:process')
const net = require('node:net')
const fs = require('node:fs')

createHook({
  init(asyncId, type, triggerAsyncId) {
    const eid = executionAsyncId()
    fs.writeSync(stdout.fd, `${type}(${asyncId}): trigger: ${triggerAsyncId} execution: ${eid}\n`)
  },
}).enable()

net.createServer(conn => {}).listen(8080)

Вывод при обращении к серверу с помощью nc localhost 8080:

bash
TCPSERVERWRAP(5): trigger: 1 execution: 1
TCPWRAP(7): trigger: 5 execution: 0

TCPSERVERWRAP — это сервер, который принимает соединения.

TCPWRAP — это новое соединение от клиента. При установлении нового соединения немедленно создается экземпляр TCPWrap. Это происходит вне любого стека JavaScript. (Значение executionAsyncId() равное 0 означает, что оно выполняется из C++ без стека JavaScript над ним.) Имея только эту информацию, было бы невозможно связать ресурсы с точки зрения того, что вызвало их создание, поэтому triggerAsyncId получает задачу распространения того, какой ресурс отвечает за существование нового ресурса.

resource

resource — это объект, представляющий фактический асинхронный ресурс, который был инициализирован. API для доступа к объекту может быть определен создателем ресурса. Ресурсы, созданные самим Node.js, являются внутренними и могут измениться в любое время. Поэтому для них не указан API.

В некоторых случаях объект ресурса повторно используется для повышения производительности, поэтому небезопасно использовать его в качестве ключа в WeakMap или добавлять к нему свойства.

Пример асинхронного контекста

Случай использования отслеживания контекста охватывается стабильным API AsyncLocalStorage. Этот пример лишь иллюстрирует работу асинхронных хуков, но AsyncLocalStorage лучше подходит для этого случая.

Ниже приведен пример с дополнительной информацией о вызовах init между вызовами before и after, в частности, как будет выглядеть обратный вызов listen(). Форматирование вывода немного более сложное, чтобы было легче видеть контекст вызова.

js
import async_hooks from 'node:async_hooks'
import fs from 'node:fs'
import net from 'node:net'
import { stdout } from 'node:process'
const { fd } = stdout

let indent = 0
async_hooks
  .createHook({
    init(asyncId, type, triggerAsyncId) {
      const eid = async_hooks.executionAsyncId()
      const indentStr = ' '.repeat(indent)
      fs.writeSync(fd, `${indentStr}${type}(${asyncId}):` + ` trigger: ${triggerAsyncId} execution: ${eid}\n`)
    },
    before(asyncId) {
      const indentStr = ' '.repeat(indent)
      fs.writeSync(fd, `${indentStr}before:  ${asyncId}\n`)
      indent += 2
    },
    after(asyncId) {
      indent -= 2
      const indentStr = ' '.repeat(indent)
      fs.writeSync(fd, `${indentStr}after:  ${asyncId}\n`)
    },
    destroy(asyncId) {
      const indentStr = ' '.repeat(indent)
      fs.writeSync(fd, `${indentStr}destroy:  ${asyncId}\n`)
    },
  })
  .enable()

net
  .createServer(() => {})
  .listen(8080, () => {
    // Давайте подождем 10 мс перед тем, как залогировать запуск сервера.
    setTimeout(() => {
      console.log('>>>', async_hooks.executionAsyncId())
    }, 10)
  })
js
const async_hooks = require('node:async_hooks')
const fs = require('node:fs')
const net = require('node:net')
const { fd } = process.stdout

let indent = 0
async_hooks
  .createHook({
    init(asyncId, type, triggerAsyncId) {
      const eid = async_hooks.executionAsyncId()
      const indentStr = ' '.repeat(indent)
      fs.writeSync(fd, `${indentStr}${type}(${asyncId}):` + ` trigger: ${triggerAsyncId} execution: ${eid}\n`)
    },
    before(asyncId) {
      const indentStr = ' '.repeat(indent)
      fs.writeSync(fd, `${indentStr}before:  ${asyncId}\n`)
      indent += 2
    },
    after(asyncId) {
      indent -= 2
      const indentStr = ' '.repeat(indent)
      fs.writeSync(fd, `${indentStr}after:  ${asyncId}\n`)
    },
    destroy(asyncId) {
      const indentStr = ' '.repeat(indent)
      fs.writeSync(fd, `${indentStr}destroy:  ${asyncId}\n`)
    },
  })
  .enable()

net
  .createServer(() => {})
  .listen(8080, () => {
    // Давайте подождем 10 мс перед тем, как залогировать запуск сервера.
    setTimeout(() => {
      console.log('>>>', async_hooks.executionAsyncId())
    }, 10)
  })

Вывод только при запуске сервера:

bash
TCPSERVERWRAP(5): trigger: 1 execution: 1
TickObject(6): trigger: 5 execution: 1
before:  6
  Timeout(7): trigger: 6 execution: 6
after:   6
destroy: 6
before:  7
>>> 7
  TickObject(8): trigger: 7 execution: 7
after:   7
before:  8
after:   8

Как показано в примере, executionAsyncId() и execution указывают значение текущего контекста выполнения; который определяется вызовами before и after.

Использование только execution для отображения распределения ресурсов приводит к следующему:

bash
  root(1)
     ^
     |
TickObject(6)
     ^
     |
 Timeout(7)

TCPSERVERWRAP не является частью этого графа, хотя именно он был причиной вызова console.log(). Это связано с тем, что привязка к порту без имени хоста является синхронной операцией, но для поддержания полностью асинхронного API обратный вызов пользователя помещается в process.nextTick(). Именно поэтому в выводе присутствует TickObject и является "родителем" для обратного вызова .listen().

Граф показывает только когда был создан ресурс, а не почему, поэтому для отслеживания почему используйте triggerAsyncId. Что можно представить следующим графом:

bash
 bootstrap(1)
     |
     ˅
TCPSERVERWRAP(5)
     |
     ˅
 TickObject(6)
     |
     ˅
  Timeout(7)

before(asyncId)

Когда инициируется асинхронная операция (например, TCP-сервер получает новое соединение) или завершается (например, запись данных на диск), вызывается обратный вызов для уведомления пользователя. Обратный вызов before вызывается непосредственно перед выполнением указанного обратного вызова. asyncId — это уникальный идентификатор, присвоенный ресурсу, который собирается выполнить обратный вызов.

Обратный вызов before будет вызван от 0 до N раз. Обратный вызов before обычно будет вызван 0 раз, если асинхронная операция была отменена или, например, если TCP-сервер не получил никаких соединений. Постоянные асинхронные ресурсы, такие как TCP-сервер, обычно будут вызывать обратный вызов before несколько раз, в то время как другие операции, такие как fs.open(), вызовут его только один раз.

after(asyncId)

Вызывается сразу после завершения обратного вызова, указанного в before.

Если во время выполнения обратного вызова возникает необработанное исключение, то after будет выполнен после того, как будет сгенерировано событие 'uncaughtException' или запустится обработчик domain.

destroy(asyncId)

Вызывается после уничтожения ресурса, соответствующего asyncId. Он также вызывается асинхронно из API встраивания emitDestroy().

Некоторые ресурсы зависят от сборки мусора для очистки, поэтому, если есть ссылка на объект resource, переданный в init, возможно, что destroy никогда не будет вызван, что приведет к утечке памяти в приложении. Если ресурс не зависит от сборки мусора, то это не будет проблемой.

Использование хука destroy приводит к дополнительным накладным расходам, поскольку это позволяет отслеживать экземпляры Promise с помощью сборщика мусора.

promiseResolve(asyncId)

Добавлено в: v8.6.0

Вызывается, когда вызывается функция resolve, переданная конструктору Promise (либо напрямую, либо с помощью других средств разрешения обещания).

resolve() не выполняет никакой наблюдаемой синхронной работы.

Promise не обязательно выполняется или отклоняется в этот момент, если Promise был разрешен путем принятия состояния другого Promise.

js
new Promise(resolve => resolve(true)).then(a => {})

вызывает следующие обратные вызовы:

text
init для PROMISE с id 5, id триггера: 1
  promise resolve 5      # соответствует resolve(true)
init для PROMISE с id 6, id триггера: 5  # Promise, возвращенный then()
  before 6               # вход в обратный вызов then()
  promise resolve 6      # обратный вызов then() разрешает promise путем возврата
  after 6

async_hooks.executionAsyncResource()

Добавлено в: v13.9.0, v12.17.0

  • Возвращает: <Объект> Ресурс, представляющий текущее выполнение. Полезен для хранения данных внутри ресурса.

Объекты ресурсов, возвращаемые executionAsyncResource(), чаще всего являются внутренними объектами Node.js с недокументированными API. Использование любых функций или свойств объекта, вероятно, приведет к сбою вашего приложения, и этого следует избегать.

Использование executionAsyncResource() в контексте выполнения верхнего уровня вернет пустой объект, так как нет объекта дескриптора или запроса для использования, но наличие объекта, представляющего верхний уровень, может быть полезным.

js
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
})
js
const { open } = require('node:fs')
const { executionAsyncId, executionAsyncResource } = require('node:async_hooks')

console.log(executionAsyncId(), executionAsyncResource()) // 1 {}
open(__filename, 'r', (err, fd) => {
  console.log(executionAsyncId(), executionAsyncResource()) // 7 FSReqWrap
})

Это можно использовать для реализации локального хранилища продолжения без использования отслеживающей Map для хранения метаданных:

js
import { createServer } from 'node:http'
import { executionAsyncId, executionAsyncResource, createHook } from 'node:async_hooks'
const sym = Symbol('state') // Приватный символ, чтобы избежать загрязнения

createHook({
  init(asyncId, type, triggerAsyncId, resource) {
    const cr = executionAsyncResource()
    if (cr) {
      resource[sym] = cr[sym]
    }
  },
}).enable()

const server = createServer((req, res) => {
  executionAsyncResource()[sym] = { state: req.url }
  setTimeout(function () {
    res.end(JSON.stringify(executionAsyncResource()[sym]))
  }, 100)
}).listen(3000)
js
const { createServer } = require('node:http')
const { executionAsyncId, executionAsyncResource, createHook } = require('node:async_hooks')
const sym = Symbol('state') // Приватный символ, чтобы избежать загрязнения

createHook({
  init(asyncId, type, triggerAsyncId, resource) {
    const cr = executionAsyncResource()
    if (cr) {
      resource[sym] = cr[sym]
    }
  },
}).enable()

const server = createServer((req, res) => {
  executionAsyncResource()[sym] = { state: req.url }
  setTimeout(function () {
    res.end(JSON.stringify(executionAsyncResource()[sym]))
  }, 100)
}).listen(3000)

async_hooks.executionAsyncId()

[История]

ВерсияИзменения
v8.2.0Переименовано из currentId.
v8.1.0Добавлено в: v8.1.0
  • Возвращает: <number> asyncId текущего контекста выполнения. Полезно для отслеживания момента вызова чего-либо.
js
import { executionAsyncId } from 'node:async_hooks'
import fs from 'node:fs'

console.log(executionAsyncId()) // 1 - начальная загрузка
const path = '.'
fs.open(path, 'r', (err, fd) => {
  console.log(executionAsyncId()) // 6 - open()
})
js
const async_hooks = require('node:async_hooks')
const fs = require('node:fs')

console.log(async_hooks.executionAsyncId()) // 1 - начальная загрузка
const path = '.'
fs.open(path, 'r', (err, fd) => {
  console.log(async_hooks.executionAsyncId()) // 6 - open()
})

ID, возвращаемый executionAsyncId(), связан со временем выполнения, а не причинностью (которая охватывается triggerAsyncId()):

js
const server = net
  .createServer(conn => {
    // Возвращает ID сервера, а не нового соединения, потому что
    // обратный вызов выполняется в области выполнения MakeCallback() сервера.
    async_hooks.executionAsyncId()
  })
  .listen(port, () => {
    // Возвращает ID TickObject (process.nextTick()), потому что все
    // обратные вызовы, переданные .listen(), обернуты в nextTick().
    async_hooks.executionAsyncId()
  })

Контексты Promise могут не получать точные executionAsyncIds по умолчанию. Смотрите раздел о отслеживании выполнения Promise.

async_hooks.triggerAsyncId()

  • Возвращает: <number> ID ресурса, ответственного за вызов обратного вызова, который выполняется в данный момент.
js
const server = net
  .createServer(conn => {
    // Ресурс, который вызвал (или запустил) вызов этого обратного вызова,
    // был ресурсом нового соединения. Таким образом, возвращаемое значение triggerAsyncId()
    // является asyncId "conn".
    async_hooks.triggerAsyncId()
  })
  .listen(port, () => {
    // Даже если все обратные вызовы, переданные .listen(), обернуты в nextTick(),
    // сам обратный вызов существует из-за вызова server.listen().
    // Таким образом, возвращаемым значением будет ID сервера.
    async_hooks.triggerAsyncId()
  })

Контексты Promise могут не получать действительные triggerAsyncId по умолчанию. Смотрите раздел о отслеживании выполнения Promise.

async_hooks.asyncWrapProviders

Добавлено в: v17.2.0, v16.14.0

  • Возвращает: Карта типов провайдеров в соответствующие числовые идентификаторы. Эта карта содержит все типы событий, которые могут быть сгенерированы событием async_hooks.init().

Эта функция подавляет устаревшее использование process.binding('async_wrap').Providers. См.: DEP0111

Отслеживание выполнения Promise

По умолчанию выполнения Promise не присваиваются asyncId из-за относительно затратного характера API интроспекции Promise, предоставляемого V8. Это означает, что программы, использующие Promise или async/await, по умолчанию не будут получать правильные идентификаторы выполнения и триггера для контекстов обратного вызова Promise.

js
import { executionAsyncId, triggerAsyncId } from 'node:async_hooks'

Promise.resolve(1729).then(() => {
  console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`)
})
// выводит:
// eid 1 tid 0
js
const { executionAsyncId, triggerAsyncId } = require('node:async_hooks')

Promise.resolve(1729).then(() => {
  console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`)
})
// выводит:
// eid 1 tid 0

Обратите внимание, что обратный вызов then() утверждает, что был выполнен в контексте внешней области видимости, несмотря на то, что произошло асинхронное переключение. Также значение triggerAsyncId равно 0, что означает, что мы упускаем контекст о ресурсе, который вызвал (запустил) выполнение обратного вызова then().

Установка асинхронных хуков через async_hooks.createHook включает отслеживание выполнения Promise:

js
import { createHook, executionAsyncId, triggerAsyncId } from 'node:async_hooks'
createHook({ init() {} }).enable() // принудительно включает PromiseHooks.
Promise.resolve(1729).then(() => {
  console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`)
})
// выводит:
// eid 7 tid 6
js
const { createHook, executionAsyncId, triggerAsyncId } = require('node:async_hooks')

createHook({ init() {} }).enable() // принудительно включает PromiseHooks.
Promise.resolve(1729).then(() => {
  console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`)
})
// выводит:
// eid 7 tid 6

В этом примере добавление любой фактической функции хука включило отслеживание Promise. В приведенном выше примере есть два Promise; Promise, созданный Promise.resolve(), и Promise, возвращенный вызовом then(). В приведенном выше примере первый Promise получил asyncId 6, а последний получил asyncId 7. Во время выполнения обратного вызова then() мы выполняемся в контексте Promise с asyncId 7. Этот Promise был запущен асинхронным ресурсом 6.

Еще одна тонкость с Promise заключается в том, что обратные вызовы before и after выполняются только для связанных Promise. Это означает, что Promise, не созданные с помощью then()/catch(), не будут иметь обратных вызовов before и after, вызванных для них. Для получения дополнительной информации смотрите подробности API V8 PromiseHooks.

JavaScript embedder API

Разработчики библиотек, которые обрабатывают собственные асинхронные ресурсы, выполняющие такие задачи, как ввод-вывод, пулы соединений или управление очередями обратных вызовов, могут использовать JavaScript API AsyncResource, чтобы вызывались все соответствующие обратные вызовы.

Класс: AsyncResource

Документация для этого класса перемещена AsyncResource.

Класс: AsyncLocalStorage

Документация для этого класса перемещена AsyncLocalStorage.