Асинхронные хуки
[Стабильность: 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, которые могут охватить большинство его вариантов использования, включают:
AsyncLocalStorage
отслеживает асинхронный контекстprocess.getActiveResourcesInfo()
отслеживает активные ресурсы
Модуль node:async_hooks
предоставляет API для отслеживания асинхронных ресурсов. К нему можно получить доступ, используя:
import async_hooks from 'node:async_hooks'
const async_hooks = require('node:async_hooks')
Терминология
Асинхронный ресурс представляет собой объект со связанной обратной связью. Эта обратная связь может вызываться несколько раз, например, событие 'connection'
в net.createServer()
, или только один раз, как в fs.open()
. Ресурс также может быть закрыт до вызова обратной связи. AsyncHook
не делает явного различия между этими различными случаями, но будет представлять их как абстрактную концепцию, которая является ресурсом.
Если используются Worker
, каждый поток имеет независимый интерфейс async_hooks
, и каждый поток будет использовать новый набор асинхронных идентификаторов.
Обзор
Ниже представлен краткий обзор публичного API.
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) {}
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
callbacks
<Object> Хук-колбеки для регистрации.init
<Function>init
колбек.before
<Function>before
колбек.after
<Function>after
колбек.destroy
<Function>destroy
колбек.promiseResolve
<Function>promiseResolve
колбек.
Возвращает: <AsyncHook> Экземпляр, используемый для отключения и включения хуков
Регистрирует функции для вызова для различных событий жизненного цикла каждой асинхронной операции.
Колбеки init()
/before()
/after()
/destroy()
вызываются для соответствующих асинхронных событий в течение жизненного цикла ресурса.
Все колбеки необязательны. Например, если нужно отслеживать только очистку ресурса, то нужно передать только колбек destroy
. Подробности обо всех функциях, которые могут быть переданы в callbacks
, находятся в разделе Хук-колбеки.
import { createHook } from 'node:async_hooks'
const asyncHook = createHook({
init(asyncId, type, triggerAsyncId, resource) {},
destroy(asyncId) {},
})
const async_hooks = require('node:async_hooks')
const asyncHook = async_hooks.createHook({
init(asyncId, type, triggerAsyncId, resource) {},
destroy(asyncId) {},
})
Колбеки будут унаследованы через цепочку прототипов:
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
рекурсивно, поскольку это синхронная операция.
import { writeFileSync } from 'node:fs'
import { format } from 'node:util'
function debug(...args) {
// Используйте подобную функцию при отладке внутри обратного вызова AsyncHook
writeFileSync('log.out', `${format(...args)}\n`, { flag: 'a' })
}
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
должен быть включен сразу после создания, можно использовать следующий шаблон.
import { createHook } from 'node:async_hooks'
const hook = createHook(callbacks).enable()
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
, только то, что такая возможность существует.
Это поведение можно наблюдать, сделав что-то вроде открытия ресурса, а затем закрытия его до того, как ресурс может быть использован. Следующий фрагмент демонстрирует это.
import { createServer } from 'node:net'
createServer().listen(function () {
this.close()
})
// ИЛИ
clearTimeout(setTimeout(() => {}, 10))
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
:
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)
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
:
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()
. Форматирование вывода немного более сложное, чтобы было легче видеть контекст вызова.
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)
})
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)
})
Вывод только при запуске сервера:
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
для отображения распределения ресурсов приводит к следующему:
root(1)
^
|
TickObject(6)
^
|
Timeout(7)
TCPSERVERWRAP
не является частью этого графа, хотя именно он был причиной вызова console.log()
. Это связано с тем, что привязка к порту без имени хоста является синхронной операцией, но для поддержания полностью асинхронного API обратный вызов пользователя помещается в process.nextTick()
. Именно поэтому в выводе присутствует TickObject
и является "родителем" для обратного вызова .listen()
.
Граф показывает только когда был создан ресурс, а не почему, поэтому для отслеживания почему используйте triggerAsyncId
. Что можно представить следующим графом:
bootstrap(1)
|
˅
TCPSERVERWRAP(5)
|
˅
TickObject(6)
|
˅
Timeout(7)
before(asyncId)
asyncId
<number>
Когда инициируется асинхронная операция (например, TCP-сервер получает новое соединение) или завершается (например, запись данных на диск), вызывается обратный вызов для уведомления пользователя. Обратный вызов before
вызывается непосредственно перед выполнением указанного обратного вызова. asyncId
— это уникальный идентификатор, присвоенный ресурсу, который собирается выполнить обратный вызов.
Обратный вызов before
будет вызван от 0 до N раз. Обратный вызов before
обычно будет вызван 0 раз, если асинхронная операция была отменена или, например, если TCP-сервер не получил никаких соединений. Постоянные асинхронные ресурсы, такие как TCP-сервер, обычно будут вызывать обратный вызов before
несколько раз, в то время как другие операции, такие как fs.open()
, вызовут его только один раз.
after(asyncId)
asyncId
<number>
Вызывается сразу после завершения обратного вызова, указанного в before
.
Если во время выполнения обратного вызова возникает необработанное исключение, то after
будет выполнен после того, как будет сгенерировано событие 'uncaughtException'
или запустится обработчик domain
.
destroy(asyncId)
asyncId
<number>
Вызывается после уничтожения ресурса, соответствующего asyncId
. Он также вызывается асинхронно из API встраивания emitDestroy()
.
Некоторые ресурсы зависят от сборки мусора для очистки, поэтому, если есть ссылка на объект resource
, переданный в init
, возможно, что destroy
никогда не будет вызван, что приведет к утечке памяти в приложении. Если ресурс не зависит от сборки мусора, то это не будет проблемой.
Использование хука destroy приводит к дополнительным накладным расходам, поскольку это позволяет отслеживать экземпляры Promise
с помощью сборщика мусора.
promiseResolve(asyncId)
Добавлено в: v8.6.0
asyncId
<number>
Вызывается, когда вызывается функция resolve
, переданная конструктору Promise
(либо напрямую, либо с помощью других средств разрешения обещания).
resolve()
не выполняет никакой наблюдаемой синхронной работы.
Promise
не обязательно выполняется или отклоняется в этот момент, если Promise
был разрешен путем принятия состояния другого Promise
.
new Promise(resolve => resolve(true)).then(a => {})
вызывает следующие обратные вызовы:
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()
в контексте выполнения верхнего уровня вернет пустой объект, так как нет объекта дескриптора или запроса для использования, но наличие объекта, представляющего верхний уровень, может быть полезным.
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
})
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
для хранения метаданных:
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)
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
текущего контекста выполнения. Полезно для отслеживания момента вызова чего-либо.
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()
})
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()
):
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 ресурса, ответственного за вызов обратного вызова, который выполняется в данный момент.
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.
import { executionAsyncId, triggerAsyncId } from 'node:async_hooks'
Promise.resolve(1729).then(() => {
console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`)
})
// выводит:
// eid 1 tid 0
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:
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
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
.