Асинхронные хуки¶
Стабильность: 1 – Экспериментальная
Пожалуйста, мигрируйте от этого API, если можете. Мы не рекомендуем использовать API createHook
, AsyncHook
и executionAsyncResource
, так как они имеют проблемы с удобством использования, риски для безопасности и влияют на производительность. Для случаев использования отслеживания асинхронного контекста лучше использовать стабильный API AsyncLocalStorage
. Если у вас есть сценарий использования createHook
, AsyncHook
или executionAsyncResource
, выходящий за рамки потребностей отслеживания контекста, решаемых AsyncLocalStorage
или диагностических данных, предоставляемых в настоящее время Diagnostics Channel, пожалуйста, откройте проблему по адресу https://github.com/nodejs/node/issues, описав ваш сценарий использования, чтобы мы могли создать API, более ориентированный на конкретные цели.
Мы настоятельно не рекомендуем использовать API async_hooks
. Другие API, которые могут покрыть большинство случаев использования, включают:
AsyncLocalStorage
пути async контекстаprocess.getActiveResourcesInfo()
отслеживает активные ресурсы
Модуль node:async_hooks
предоставляет API для отслеживания асинхронных ресурсов. Доступ к нему можно получить, используя:
1 |
|
1 |
|
Терминология¶
Асинхронный ресурс представляет собой объект с ассоциированным обратным вызовом. Этот обратный вызов может быть вызван несколько раз, например, событие 'connection'
в net.createServer()
, или только один раз, как в fs.open()
. Ресурс также может быть закрыт до вызова обратного вызова. AsyncHook
не делает явного различия между этими разными случаями, но будет представлять их как абстрактную концепцию, которой является ресурс.
Если используется Worker
s, каждый поток имеет независимый интерфейс async_hooks
, и каждый поток будет использовать новый набор идентификаторов async.
Обзор¶
Ниже приведен простой обзор публичного API.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
|
async_hooks.createHook(callbacks)
¶
callbacks
<Object>
Обратные вызовы хука для регистрацииinit
<Function>
Обратный вызовinit
.before
<Function>
Обратный вызовbefore
.after
<Function>
Обратный вызовafter
.destroy
<Function>
Обратный вызовdestroy
.promiseResolve
<Function>
Обратный вызовpromiseResolve
.
- Возвращает:
AsyncHook
Экземпляр, используемый для отключения и включения хуков.
Регистрирует функции, которые будут вызываться для различных событий времени жизни каждой асинхронной операции.
Обратные вызовы init()
/ before()
/ after()
/ destroy()
вызываются для соответствующего асинхронного события в течение времени жизни ресурса.
Все обратные вызовы необязательны. Например, если необходимо отслеживать только очистку ресурса, то нужно передать только обратный вызов destroy
. Специфика всех функций, которые могут быть переданы в callbacks
, находится в разделе Hook Callbacks.
1 2 3 4 5 6 |
|
1 2 3 4 5 6 |
|
Обратные вызовы будут наследоваться через цепочку прототипов:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Поскольку промисы являются асинхронными ресурсами, жизненный цикл которых отслеживается через механизм асинхронных крючков, обратные вызовы init()
, before()
, after()
и destroy()
_не должны быть асинхронными функциями, возвращающими промисы.
Обработка ошибок¶
Если какой-либо обратный вызов AsyncHook
отбрасывает ошибку, приложение выводит трассировку стека и завершает работу. Путь завершения следует за не пойманным исключением, но все слушатели 'uncaughtException'
удаляются, тем самым заставляя процесс завершиться. Обратные вызовы 'exit'
по-прежнему будут вызываться, если только приложение не запущено с --abort-on-uncaught-exception
, в этом случае будет напечатана трассировка стека и приложение выйдет, оставив файл ядра.
Причина такого поведения при обработке ошибок заключается в том, что эти обратные вызовы выполняются в потенциально изменчивые моменты жизни объекта, например, во время создания и уничтожения класса. В связи с этим считается необходимым быстро завершить процесс, чтобы предотвратить непреднамеренное прерывание в будущем. Это может быть изменено в будущем, если будет проведен всесторонний анализ, чтобы убедиться, что исключение может следовать нормальному потоку управления без непреднамеренных побочных эффектов.
Печать в обратных вызовах AsyncHook
¶
Поскольку печать на консоль является асинхронной операцией, console.log()
вызовет обратные вызовы AsyncHook
. Использование console.log()
или подобных асинхронных операций внутри функции обратного вызова AsyncHook
приведет к бесконечной рекурсии. Простым решением этой проблемы при отладке является использование синхронной операции протоколирования, такой как fs.writeFileSync(file, msg, flag)
. Это приведет к печати в файл и не будет рекурсивно вызывать AsyncHook
, поскольку она синхронна.
1 2 3 4 5 6 7 8 9 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Если асинхронная операция необходима для логирования, можно отслеживать, что вызвало асинхронную операцию, используя информацию, предоставляемую самим AsyncHook
. Тогда логирование должно быть пропущено, если именно логирование вызвало обратный вызов AsyncHook
. Таким образом, прерывается бесконечная рекурсия.
Класс: AsyncHook
¶
Класс AsyncHook
предоставляет интерфейс для отслеживания событий времени жизни асинхронных операций.
asyncHook.enable()
¶
- Возвращает:
AsyncHook
Ссылка наasyncHook
.
Включает обратные вызовы для данного экземпляра AsyncHook
. Если обратные вызовы не предоставлены, включение не имеет смысла.
По умолчанию экземпляр AsyncHook
отключен. Если экземпляр AsyncHook
должен быть включен сразу после создания, можно использовать следующий шаблон.
1 2 3 |
|
1 2 3 |
|
asyncHook.disable()
¶
- Возвращает:
AsyncHook
Ссылка наasyncHook
.
Отключает обратные вызовы для данного экземпляра AsyncHook
из глобального пула обратных вызовов AsyncHook
для выполнения. После отключения хука он не будет вызываться снова, пока не будет включен.
Для согласованности API disable()
также возвращает экземпляр AsyncHook
.
Обратные вызовы крючков¶
Ключевые события во время жизни асинхронных событий были разделены на четыре области: инстанцирование, до/после вызова обратного вызова, и когда экземпляр уничтожается.
init(asyncId, type, triggerAsyncId, resource)
¶
asyncId
<number>
Уникальный идентификатор для ресурса async.type
<string>
Тип асинхронного ресурса.triggerAsyncId
<number>
Уникальный ID ресурса async, в контексте выполнения которого был создан данный ресурс async.resource
<Object>
Ссылка на ресурс, представляющий асинхронную операцию, должен быть освобожден во время destroy.
Вызывается при создании класса, который имеет возможность испускать асинхронное событие. Это не означает, что экземпляр должен вызвать before
/after
перед вызовом destroy
, только то, что такая возможность существует.
Такое поведение можно наблюдать, если сделать что-то вроде открытия ресурса, а затем закрыть его до того, как ресурс может быть использован. Следующий фрагмент демонстрирует это.
1 2 3 4 5 6 7 |
|
1 2 3 4 5 6 7 |
|
Каждому новому ресурсу присваивается идентификатор, уникальный в пределах текущего экземпляра Node.js.
type
¶
type
- это строка, идентифицирующая тип ресурса, который вызвал вызов init
. Как правило, она соответствует имени конструктора ресурса.
Тип type
ресурсов, создаваемых самим Node.js, может измениться в любом выпуске Node.js. Допустимые значения включают TLSWRAP
, TCPWRAP
, TCPSERVERWRAP
, GETADDRINFOREQWRAP
, FSREQCALLBACK
, Microtask
и Timeout
. Для получения полного списка обратитесь к исходному коду используемой версии Node.js.
Кроме того, пользователи AsyncResource
создают асинхронные ресурсы независимо от самого Node.js.
Существует также тип ресурса PROMISE
, который используется для отслеживания экземпляров Promise
и запланированных ими асинхронных работ.
Пользователи могут определить свой собственный тип
при использовании публичного API embedder.
Возможны столкновения имен типов. Встраивателям рекомендуется использовать уникальные префиксы, такие как имя пакета npm, чтобы избежать коллизий при прослушивании хуков.
triggerAsyncId
¶
triggerAsyncId
- это asyncId
ресурса, который вызвал (или "запустил") инициализацию нового ресурса и вызвал вызов init
. Это отличается от async_hooks.executionAsyncId()
, который показывает только когда был создан ресурс, в то время как triggerAsyncId
показывает почему был создан ресурс.
Ниже приведена простая демонстрация triggerAsyncId
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Вывод при обращении к серверу с nc localhost 8080
:
1 2 |
|
TCPSERVERWRAP
- это сервер, который принимает соединения.
TCPWRAP
- это новое соединение от клиента. Когда создается новое соединение, немедленно создается экземпляр TCPWrap
. Это происходит вне любого стека JavaScript. (Значение executionAsyncId()
равное 0
означает, что это выполняется из C++ без стека JavaScript над ним). Имея только эту информацию, было бы невозможно связать ресурсы вместе с точки зрения того, что вызвало их создание, поэтому triggerAsyncId
получает задачу распространить информацию о том, какой ресурс ответственен за существование нового ресурса.
resource
¶
resource
- это объект, представляющий реальный асинхронный ресурс, который был инициализирован. API для доступа к объекту может быть указан создателем ресурса. Ресурсы, созданные самим Node.js, являются внутренними и могут изменяться в любое время. Поэтому для них не указывается API.
В некоторых случаях объект ресурса используется повторно по причинам производительности, поэтому небезопасно использовать его в качестве ключа в WeakMap
или добавлять к нему свойства.
Пример асинхронного контекста¶
Случай использования отслеживания контекста покрывается стабильным API AsyncLocalStorage
. Этот пример только иллюстрирует работу асинхронных крючков, но AsyncLocalStorage
лучше подходит для этого случая использования.
Ниже приведен пример с дополнительной информацией о вызовах init
между вызовами before
и after
, в частности о том, как будет выглядеть обратный вызов listen()
. Форматирование вывода немного более сложное, чтобы было легче увидеть контекст вызова.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
|
Вывод только при запуске сервера:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Как показано в примере, executionAsyncId()
и execution
указывают значение текущего контекста выполнения, который определяется вызовами before
и after
.
Только использование execution
для построения графика распределения ресурсов приводит к следующему:
1 2 3 4 5 6 7 |
|
Привязка TCPSERVERWRAP
не является частью этого графика, хотя она была причиной вызова console.log()
. Это происходит потому, что привязка к порту без имени хоста является синхронной операцией, но для поддержания полностью асинхронного API обратный вызов пользователя помещается в process.nextTick()
. Именно поэтому TickObject
присутствует в выводе и является "родителем" для обратного вызова .listen()
.
График показывает только когда был создан ресурс, но не почему, поэтому для отслеживания почему используйте triggerAsyncId
. Что может быть представлено следующим графиком:
1 2 3 4 5 6 7 8 9 10 |
|
before(asyncId)
¶
asyncId
<number>
Когда асинхронная операция инициируется (например, TCP-сервер получает новое соединение) или завершается (например, запись данных на диск), вызывается обратный вызов для уведомления пользователя. Обратный вызов before
вызывается непосредственно перед выполнением указанного обратного вызова. asyncId
- это уникальный идентификатор, присвоенный ресурсу, который собирается выполнить обратный вызов.
Обратный вызов before
будет вызван от 0 до N раз. Обратный вызов before
обычно вызывается 0 раз, если асинхронная операция была отменена или, например, если TCP-сервер не получает соединений. Постоянные асинхронные ресурсы, такие как TCP-сервер, обычно вызывают обратный вызов before
несколько раз, в то время как другие операции, такие как fs.open()
, вызывают его только один раз.
after(asyncId)
¶
asyncId
<number>
Вызывается сразу после завершения обратного вызова, указанного в before
.
Если во время выполнения обратного вызова произойдет не пойманное исключение, то after
будет запущен после того, как будет выдано событие 'uncaughtException'
или запущен обработчик домена
.
destroy(asyncId)
¶
asyncId
<number>
Вызывается после уничтожения ресурса, соответствующего asyncId
. Также вызывается асинхронно из API embedder emitDestroy()
.
Некоторые ресурсы зависят от сборки мусора для очистки, поэтому если ссылка на объект resource
, переданный в init
, сделана, то возможно, что destroy
никогда не будет вызван, что приведет к утечке памяти в приложении. Если ресурс не зависит от сборки мусора, то это не будет проблемой.
Использование хука destroy приводит к дополнительным накладным расходам, поскольку он позволяет отслеживать экземпляры Promise
с помощью сборщика мусора.
promiseResolve(asyncId)
¶
asyncId
<number>
Вызывается, когда вызывается функция resolve
, переданная в конструктор Promise
(напрямую или с помощью других средств разрешения промиса).
resolve()
не выполняет никакой наблюдаемой синхронной работы.
Обещание не обязательно будет выполнено или отвергнуто в этот момент, если Promise
было разрешено путем принятия состояния другого Promise
.
1 |
|
вызывает следующие обратные вызовы:
1 2 3 4 5 6 |
|
async_hooks.executionAsyncResource()
¶
- Возвращает:
<Object>
Ресурс, представляющий текущее выполнение. Полезно для хранения данных внутри ресурса.
Объекты ресурсов, возвращаемые executionAsyncResource()
, чаще всего являются внутренними объектами-ручками Node.js с недокументированными API. Использование любых функций или свойств этого объекта, скорее всего, приведет к краху вашего приложения, и его следует избегать.
Использование executionAsyncResource()
в контексте выполнения верхнего уровня вернет пустой объект, поскольку нет объекта handle или request для использования, но наличие объекта, представляющего верхний уровень, может быть полезным.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Это можно использовать для реализации локального хранилища продолжения без использования отслеживающего Map
для хранения метаданных:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
async_hooks.executionAsyncId()
¶
- Возвращает:
<number>
asyncId
текущего контекста выполнения. Полезно для отслеживания того, когда что-то вызывается.
1 2 3 4 5 6 7 |
|
1 2 3 4 5 6 7 |
|
ID, возвращаемый из executionAsyncId()
, связан с временем выполнения, а не с причинно-следственной связью (которая покрывается triggerAsyncId()
):
1 2 3 4 5 6 7 8 9 10 11 |
|
Контексты промисов могут не получать точные executionAsyncIds
по умолчанию. См. раздел отслеживание выполнения промисов.
async_hooks.triggerAsyncId()
¶
- Возвращает:
<number>
Идентификатор ресурса, ответственного за вызов обратного вызова, который выполняется в данный момент.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Контексты промисов могут не получать действительные triggerAsyncId
по умолчанию. См. раздел об отслеживании выполнения промисов.
async_hooks.asyncWrapProviders
.¶
- Возвращает: Карта типов провайдеров с соответствующим числовым идентификатором. Эта карта содержит все типы событий, которые могут быть испущены событием
async_hooks.init()
.
Эта функция подавляет устаревшее использование process.binding('async_wrap').Providers
.
Отслеживание выполнения промиса¶
По умолчанию выполнениям промисов не присваиваются asyncId
из-за относительно дорогого характера promise introspection API, предоставляемого V8. Это означает, что программы, использующие промисы или async
/await
, по умолчанию не будут получать корректные идентификаторы выполнения и триггера для контекстов обратного вызова промисов.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Обратите внимание, что обратный вызов then()
утверждает, что он был выполнен в контексте внешней области видимости, даже несмотря на асинхронный переход. Также, значение triggerAsyncId
равно 0
, что означает, что нам не хватает контекста о ресурсе, который вызвал (спровоцировал) выполнение обратного вызова then()
.
Установка асинхронных хуков с помощью async_hooks.createHook
позволяет отслеживать выполнение промисов:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
В этом примере добавление любой фактической хук-функции позволило отслеживать обещания. В приведенном примере есть два обещания: обещание, созданное Promise.resolve()
, и обещание, возвращенное вызовом then()
. В приведенном примере первое обещание получило asyncId
6
, а второе - asyncId
7
. Во время выполнения обратного вызова then()
мы выполняем в контексте обещания с asyncId
7
. Это обещание было вызвано ресурсом async 6
.
Еще одна тонкость работы с обещаниями заключается в том, что обратные вызовы before
и after
выполняются только для цепочек обещаний. Это означает, что обещания, не созданные с помощью then()
/catch()
, не будут иметь обратных вызовов before
и after
. Более подробную информацию можно найти в деталях API V8 PromiseHooks.
JavaScript embedder API¶
Разработчики библиотек, которые работают с собственными асинхронными ресурсами, выполняющими такие задачи, как ввод-вывод, пул соединений или управление очередями обратных вызовов, могут использовать JavaScript API AsyncResource
, чтобы вызывались все соответствующие обратные вызовы.
Класс: AsyncResource
¶
Документация по этому классу переехала AsyncResource
.
Класс: AsyncLocalStorage
¶
Документация по этому классу переехала AsyncLocalStorage
.