Асинхронные хуки
createHook, AsyncHook и executionAsyncResource, поскольку у них есть проблемы с удобством использования, риски безопасности и последствия для производительности. Для отслеживания асинхронного контекста лучше подходит стабильный API AsyncLocalStorage. Если вам нужен createHook, AsyncHook или executionAsyncResource помимо отслеживания контекста, решаемого с помощью AsyncLocalStorage, или диагностических данных, предоставляемых в настоящее время каналом Diagnostics Channel, создайте запрос по адресу https://github.com/nodejs/node/issues и опишите свой сценарий использования, чтобы мы могли создать API, ориентированный на конкретную задачу.Исходный код: lib/async_hooks.js
Мы настоятельно не рекомендуем использовать API async_hooks. Большинство его сценариев использования можно охватить другими API, включая:
-
AsyncLocalStorageотслеживает асинхронный контекст -
process.getActiveResourcesInfo()отслеживает активные ресурсы
Модуль node:async_hooks предоставляет API для отслеживания асинхронных ресурсов. К нему можно обратиться с помощью:
Модули JavaScript
import async_hooks from 'node:async_hooks';
CommonJS
const async_hooks = require('node:async_hooks');Терминология
Асинхронный ресурс представляет собой объект, связанный с callback-функцией. Эта функция может вызываться несколько раз, например для события 'connection' в net.createServer(), либо только один раз, как в fs.open(). Ресурс также может быть закрыт до вызова callback-функции. AsyncHook явно не различает эти случаи, а представляет их как абстрактное понятие ресурса.
Если используются объекты Worker, у каждого потока есть независимый интерфейс async_hooks, и каждый поток использует новый набор асинхронных идентификаторов.
Обзор
Ниже представлен краткий обзор публичного API.
Модули JavaScript
import async_hooks from 'node:async_hooks';
// Return the ID of the current execution context.
const eid = async_hooks.executionAsyncId();
// Return the ID of the handle responsible for triggering the callback of the
// current execution scope to call.
const tid = async_hooks.triggerAsyncId();
// Create a new AsyncHook instance. All of these callbacks are optional.
const asyncHook =
async_hooks.createHook({ init, before, after, destroy, promiseResolve });
// Allow callbacks of this AsyncHook instance to call. This is not an implicit
// action after running the constructor, and must be explicitly run to begin
// executing callbacks.
asyncHook.enable();
// Disable listening for new asynchronous events.
asyncHook.disable();
//
// The following are the callbacks that can be passed to createHook().
//
// init() is called during object construction. The resource may not have
// completed construction when this callback runs. Therefore, all fields of the
// resource referenced by "asyncId" may not have been populated.
function init(asyncId, type, triggerAsyncId, resource) { }
// before() is called just before the resource's callback is called. It can be
// called 0-N times for handles (such as TCPWrap), and will be called exactly 1
// time for requests (such as FSReqCallback).
function before(asyncId) { }
// after() is called just after the resource's callback has finished.
function after(asyncId) { }
// destroy() is called when the resource is destroyed.
function destroy(asyncId) { }
// promiseResolve() is called only for promise resources, when the
// resolve() function passed to the Promise constructor is invoked
// (either directly or through other means of resolving a promise).
function promiseResolve(asyncId) { }CommonJS
const async_hooks = require('node:async_hooks');
// Return the ID of the current execution context.
const eid = async_hooks.executionAsyncId();
// Return the ID of the handle responsible for triggering the callback of the
// current execution scope to call.
const tid = async_hooks.triggerAsyncId();
// Create a new AsyncHook instance. All of these callbacks are optional.
const asyncHook =
async_hooks.createHook({ init, before, after, destroy, promiseResolve });
// Allow callbacks of this AsyncHook instance to call. This is not an implicit
// action after running the constructor, and must be explicitly run to begin
// executing callbacks.
asyncHook.enable();
// Disable listening for new asynchronous events.
asyncHook.disable();
//
// The following are the callbacks that can be passed to createHook().
//
// init() is called during object construction. The resource may not have
// completed construction when this callback runs. Therefore, all fields of the
// resource referenced by "asyncId" may not have been populated.
function init(asyncId, type, triggerAsyncId, resource) { }
// before() is called just before the resource's callback is called. It can be
// called 0-N times for handles (such as TCPWrap), and will be called exactly 1
// time for requests (such as FSReqCallback).
function before(asyncId) { }
// after() is called just after the resource's callback has finished.
function after(asyncId) { }
// destroy() is called when the resource is destroyed.
function destroy(asyncId) { }
// promiseResolve() is called only for promise resources, when the
// resolve() function passed to the Promise constructor is invoked
// (either directly or through other means of resolving a promise).
function promiseResolve(asyncId) { }
async_hooks.createHook(callbacks)
-
callbacks<Object> Callback-функции хуков для регистрации: Hook Callbacks-
init<Function> Callback-функцияinit. -
before<Function> Callback-функцияbefore. -
after<Function> Callback-функцияafter. -
destroy<Function> Callback-функцияdestroy. -
promiseResolve<Function> Callback-функцияpromiseResolve.
-
- Возвращает: экземпляр <AsyncHook>, используемый для отключения и включения хуков
Регистрирует функции, вызываемые при различных событиях жизненного цикла каждой асинхронной операции.
Callback-функции init()/before()/after()/destroy() вызываются при соответствующих асинхронных событиях в течение жизненного цикла ресурса.
Все callback-функции необязательны. Например, если нужно отслеживать только очистку ресурса, достаточно передать callback-функцию destroy. Подробные сведения обо всех функциях, которые можно передать в callbacks, приведены в разделе Hook Callbacks.
Модули JavaScript
import { createHook } from 'node:async_hooks';
const asyncHook = createHook({
init(asyncId, type, triggerAsyncId, resource) { },
destroy(asyncId) { },
});CommonJS
const async_hooks = require('node:async_hooks');
const asyncHook = async_hooks.createHook({
init(asyncId, type, triggerAsyncId, resource) { },
destroy(asyncId) { },
});Callback-функции наследуются через цепочку прототипов:
class MyAsyncCallbacks {
init(asyncId, type, triggerAsyncId, resource) { }
destroy(asyncId) {}
}
class MyAddedCallbacks extends MyAsyncCallbacks {
before(asyncId) { }
after(asyncId) { }
}
const asyncHook = async_hooks.createHook(new MyAddedCallbacks()); copy Поскольку промисы являются асинхронными ресурсами, жизненный цикл которых отслеживается механизмом асинхронных хуков, callback-функции init(), before(), after() и destroy() не должны быть асинхронными функциями, возвращающими промисы.
Обработка ошибок
Если какая-либо callback-функция AsyncHook выбрасывает исключение, приложение выведет трассировку стека и завершит работу. Завершение происходит так же, как при необработанном исключении, однако все обработчики 'uncaughtException' удаляются, что приводит к завершению процесса. Callback-функции 'exit' всё равно будут вызваны, если только приложение не запущено с параметром --abort-on-uncaught-exception: в этом случае будет выведена трассировка стека, приложение завершится, а также будет создан файл дампа памяти.
Причина такого поведения при обработке ошибок заключается в том, что эти callback-функции выполняются в потенциально нестабильные моменты жизненного цикла объекта, например во время создания и уничтожения экземпляра класса. Поэтому считается необходимым быстро завершить процесс, чтобы предотвратить непреднамеренное аварийное завершение в будущем. В дальнейшем это поведение может измениться, если будет проведён всесторонний анализ, гарантирующий, что исключение можно обработать обычным потоком управления без непреднамеренных побочных эффектов.
Вывод данных в callback-функциях AsyncHook
Поскольку вывод в консоль является асинхронной операцией, console.log() приведёт к вызову callback-функций AsyncHook. Использование console.log() или подобных асинхронных операций внутри callback-функции AsyncHook приведёт к бесконечной рекурсии. При отладке эту проблему легко решить, используя синхронную операцию записи в журнал, например fs.writeFileSync(file, msg, flag). Она выведет данные в файл и не вызовет AsyncHook рекурсивно, поскольку является синхронной.
Модули JavaScript
import { writeFileSync } from 'node:fs';
import { format } from 'node:util';
function debug(...args) {
// Use a function like this one when debugging inside an AsyncHook callback
writeFileSync('log.out', `${format(...args)}\n`, { flag: 'a' });
}CommonJS
const fs = require('node:fs');
const util = require('node:util');
function debug(...args) {
// Use a function like this one when debugging inside an AsyncHook callback
fs.writeFileSync('log.out', `${util.format(...args)}\n`, { flag: 'a' });
}Если для записи в журнал требуется асинхронная операция, можно отслеживать причину её выполнения, используя сведения, предоставляемые самим AsyncHook. В этом случае запись в журнал следует пропускать, если именно она вызвала callback-функцию AsyncHook. Это позволит прервать бесконечную рекурсию.
Класс: AsyncHook
Класс AsyncHook предоставляет интерфейс для отслеживания событий жизненного цикла асинхронных операций.
asyncHook.enable()
- Возвращает: ссылку на <AsyncHook>
asyncHook.
Включает callback-функции для указанного экземпляра AsyncHook. Если callback-функции не заданы, включение ничего не делает.
По умолчанию экземпляр AsyncHook отключён. Если экземпляр AsyncHook нужно включить сразу после создания, можно использовать следующий шаблон.
Модули JavaScript
import { createHook } from 'node:async_hooks';
const hook = createHook(callbacks).enable();CommonJS
const async_hooks = require('node:async_hooks');
const hook = async_hooks.createHook(callbacks).enable();
asyncHook.disable()
- Возвращает: ссылку на <AsyncHook>
asyncHook.
Отключает callback-функции указанного экземпляра AsyncHook в глобальном пуле callback-функций AsyncHook, предназначенных для выполнения. После отключения хук больше не будет вызываться, пока его снова не включат.
Для согласованности API disable() также возвращает экземпляр AsyncHook.
Callback-функции хуков
Ключевые события жизненного цикла асинхронных событий разделены на четыре категории: создание, момент до и после вызова callback-функции, а также уничтожение экземпляра.
init(asyncId, type, triggerAsyncId, resource)
-
asyncId<number> Уникальный идентификатор асинхронного ресурса. -
type<string> Тип асинхронного ресурса. -
triggerAsyncId<number> Уникальный идентификатор асинхронного ресурса, в контексте выполнения которого был создан этот асинхронный ресурс. -
resource<Object> Ссылка на объект, представляющий асинхронную операцию; её необходимо освободить во время события destroy.
Вызывается при создании класса, который может инициировать асинхронное событие. Это не означает, что экземпляр обязательно должен вызвать before/after до вызова destroy, а лишь указывает на такую возможность.
Это поведение можно наблюдать, например, открыв ресурс и закрыв его до того, как его можно будет использовать. Следующий фрагмент демонстрирует такой случай.
Модули JavaScript
import { createServer } from 'node:net';
createServer().listen(function() { this.close(); });
// OR
clearTimeout(setTimeout(() => {}, 10));CommonJS
require('node:net').createServer().listen(function() { this.close(); });
// OR
clearTimeout(setTimeout(() => {}, 10));Каждому новому ресурсу назначается уникальный идентификатор в пределах текущего экземпляра 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 встраивания пользователи могут задавать собственные значения type.
Имена типов могут совпадать. Разработчикам встраиваемых систем рекомендуется использовать уникальные префиксы, например имя пакета npm, чтобы избежать совпадений при прослушивании хуков.
triggerAsyncId
triggerAsyncId — это asyncId ресурса, который вызвал (или «инициировал») инициализацию нового ресурса и привёл к вызову init. Это отличается от async_hooks.executionAsyncId(), который показывает только, когда был создан ресурс, тогда как triggerAsyncId показывает, почему он был создан.
Ниже приведён простой пример triggerAsyncId:
Модули JavaScript
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);CommonJS
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 copy
TCPSERVERWRAP — это сервер, принимающий соединения.
TCPWRAP — это новое соединение от клиента. При установлении нового соединения немедленно создаётся экземпляр TCPWrap. Это происходит вне какого-либо стека JavaScript. (executionAsyncId() со значением 0 означает, что выполнение происходит из C++ и над ним нет стека JavaScript.) Имея только эту информацию, невозможно связать ресурсы по причине их создания, поэтому triggerAsyncId отвечает за передачу сведений о том, какой ресурс отвечает за существование нового ресурса.
resource
resource — это объект, представляющий фактически инициализированный асинхронный ресурс. API для доступа к этому объекту может определяться создателем ресурса. Ресурсы, созданные самим Node.js, являются внутренними и могут измениться в любой момент. Поэтому для них не определён API.
В некоторых случаях объект ресурса повторно используется ради производительности, поэтому небезопасно использовать его в качестве ключа в WeakMap или добавлять в него свойства.
Пример асинхронного контекста
Сценарий использования для отслеживания контекста охватывается стабильным API AsyncLocalStorage. Этот пример лишь иллюстрирует работу асинхронных хуков, однако для такого сценария лучше подходит AsyncLocalStorage.
Ниже приведён пример с дополнительными сведениями о вызовах init между вызовами before и after, в частности о том, как будет выглядеть callback-функция для listen(). Формат вывода немного усложнён, чтобы облегчить понимание контекста вызова.
Модули JavaScript
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, () => {
// Let's wait 10ms before logging the server started.
setTimeout(() => {
console.log('>>>', async_hooks.executionAsyncId());
}, 10);
});CommonJS
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, () => {
// Let's wait 10ms before logging the server started.
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 copy
Как показано в примере, executionAsyncId() и execution задают значение текущего контекста выполнения; его границы определяются вызовами before и after.
Если использовать только execution для построения графа выделения ресурсов, получится следующее:
root(1)
^
|
TickObject(6)
^
|
Timeout(7) copy TCPSERVERWRAP не входит в этот граф, хотя именно он стал причиной вызова console.log(). Это происходит потому, что привязка к порту без имени хоста — синхронная операция, но для сохранения полностью асинхронного API callback-функция пользователя помещается в process.nextTick(). Поэтому TickObject присутствует в выводе и является «родительским» для callback-функции .listen().
Граф показывает только, когда был создан ресурс, но не почему. Чтобы отследить причину, используйте triggerAsyncId. Её можно представить следующим графом:
bootstrap(1)
|
˅
TCPSERVERWRAP(5)
|
˅
TickObject(6)
|
˅
Timeout(7) copy
before(asyncId)
-
asyncId<number>
Когда асинхронная операция начинается (например, TCP-сервер принимает новое соединение) или завершается (например, данные записываются на диск), для уведомления пользователя вызывается callback-функция. Callback-функция before вызывается непосредственно перед выполнением этой функции. asyncId — это уникальный идентификатор ресурса, callback-функция которого будет выполнена.
Callback-функция before будет вызвана от 0 до N раз. Callback-функция before обычно не вызывается, если асинхронная операция была отменена или, например, если TCP-сервер не получает соединений. Для постоянных асинхронных ресурсов, таких как TCP-сервер, callback-функция before обычно вызывается несколько раз, тогда как для других операций, например fs.open(), — только один раз.
after(asyncId)
-
asyncId<number>
Вызывается сразу после завершения callback-функции, указанной в before.
Если во время выполнения callback-функции возникает необработанное исключение, after будет запущен после отправки события 'uncaughtException' или выполнения обработчика domain.
destroy(asyncId)
-
asyncId<number>
Вызывается после уничтожения ресурса, соответствующего asyncId. Также вызывается асинхронно из API встраивания emitDestroy().
Некоторые ресурсы зависят от сборки мусора при очистке, поэтому ссылка на объект resource, переданный в init, может привести к тому, что destroy никогда не будет вызван, что вызовет утечку памяти в приложении. Если ресурс не зависит от сборки мусора, эта проблема не возникает.
Использование хука уничтожения приводит к дополнительным накладным расходам, поскольку включает отслеживание экземпляров Promise с помощью сборщика мусора.
promiseResolve(asyncId)
-
asyncId<number>
Вызывается, когда функция resolve, переданная конструктору Promise, выполняется напрямую или через другой способ разрешения промиса.
resolve() не выполняет никакой наблюдаемой синхронной работы.
На этом этапе Promise не обязательно находится в состоянии fulfilled или rejected, если Promise был разрешён с использованием состояния другого Promise.
new Promise((resolve) => resolve(true)).then((a) => {}); copy вызывает следующие callback-функции:
init for PROMISE with id 5, trigger id: 1 promise resolve 5 # corresponds to resolve(true) init for PROMISE with id 6, trigger id: 5 # the Promise returned by then() before 6 # the then() callback is entered promise resolve 6 # the then() callback resolves the promise by returning after 6 copy
async_hooks.executionAsyncResource()
- Возвращает: <Object> Ресурс, представляющий текущее выполнение. Полезен для хранения данных в ресурсе.
Объекты ресурсов, возвращаемые executionAsyncResource(), чаще всего являются внутренними объектами дескрипторов Node.js с недокументированными API. Использование любых функций или свойств этого объекта может привести к аварийному завершению приложения, поэтому этого следует избегать.
В контексте выполнения верхнего уровня executionAsyncResource() возвращает пустой объект, поскольку дескриптор или объект запроса отсутствует. Тем не менее объект, представляющий верхний уровень, может быть полезен.
Модули JavaScript
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
});CommonJS
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 для хранения метаданных:
Модули JavaScript
import { createServer } from 'node:http';
import {
executionAsyncId,
executionAsyncResource,
createHook,
} from 'node:async_hooks';
const sym = Symbol('state'); // Private symbol to avoid pollution
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);CommonJS
const { createServer } = require('node:http');
const {
executionAsyncId,
executionAsyncResource,
createHook,
} = require('node:async_hooks');
const sym = Symbol('state'); // Private symbol to avoid pollution
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()
- Возвращает: <number> Значение
asyncIdтекущего контекста выполнения. Полезно для отслеживания вызовов.
Модули JavaScript
import { executionAsyncId } from 'node:async_hooks';
import fs from 'node:fs';
console.log(executionAsyncId()); // 1 - bootstrap
const path = '.';
fs.open(path, 'r', (err, fd) => {
console.log(executionAsyncId()); // 6 - open()
});CommonJS
const async_hooks = require('node:async_hooks');
const fs = require('node:fs');
console.log(async_hooks.executionAsyncId()); // 1 - bootstrap
const path = '.';
fs.open(path, 'r', (err, fd) => {
console.log(async_hooks.executionAsyncId()); // 6 - open()
});Идентификатор, возвращаемый executionAsyncId(), связан со временем выполнения, а не с причинно-следственной связью (за неё отвечает triggerAsyncId()):
const server = net.createServer((conn) => {
// Returns the ID of the server, not of the new connection, because the
// callback runs in the execution scope of the server's MakeCallback().
async_hooks.executionAsyncId();
}).listen(port, () => {
// Returns the ID of a TickObject (process.nextTick()) because all
// callbacks passed to .listen() are wrapped in a nextTick().
async_hooks.executionAsyncId();
}); copy Для контекстов промисов по умолчанию могут не назначаться точные значения executionAsyncIds. См. раздел об отслеживании выполнения промисов.
async_hooks.triggerAsyncId()
- Возвращает: <number> Идентификатор ресурса, ответственного за вызов выполняемой в данный момент callback-функции.
const server = net.createServer((conn) => {
// The resource that caused (or triggered) this callback to be called
// was that of the new connection. Thus the return value of triggerAsyncId()
// is the asyncId of "conn".
async_hooks.triggerAsyncId();
}).listen(port, () => {
// Even though all callbacks passed to .listen() are wrapped in a nextTick()
// the callback itself exists because the call to the server's .listen()
// was made. So the return value would be the ID of the server.
async_hooks.triggerAsyncId();
}); copy Для контекстов промисов по умолчанию могут не назначаться корректные значения triggerAsyncId. См. раздел об отслеживании выполнения промисов.
async_hooks.asyncWrapProviders
- Возвращает: карту типов провайдеров и соответствующих им числовых идентификаторов. Эта карта содержит все типы событий, которые могут быть отправлены событием
async_hooks.init().
Эта функция подавляет устаревшее использование process.binding('async_wrap').Providers. См.: DEP0111
Отслеживание выполнения промисов
По умолчанию выполнениям промисов не назначаются значения asyncId из-за относительно высокой стоимости API интроспекции промисов, предоставляемого V8. Это означает, что программы, использующие промисы или async/await, по умолчанию не получат корректные идентификаторы выполнения и инициирующих ресурсов для контекстов callback-функций промисов.
Модули JavaScript
import { executionAsyncId, triggerAsyncId } from 'node:async_hooks';
Promise.resolve(1729).then(() => {
console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`);
});
// produces:
// eid 1 tid 0CommonJS
const { executionAsyncId, triggerAsyncId } = require('node:async_hooks');
Promise.resolve(1729).then(() => {
console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`);
});
// produces:
// eid 1 tid 0Обратите внимание: callback-функция then() утверждает, что выполнялась в контексте внешней области видимости, хотя был асинхронный переход. Кроме того, значение triggerAsyncId равно 0, то есть отсутствует информация о ресурсе, который вызвал выполнение callback-функции then().
Установка асинхронных хуков с помощью async_hooks.createHook включает отслеживание выполнения промисов:
Модули JavaScript
import { createHook, executionAsyncId, triggerAsyncId } from 'node:async_hooks';
createHook({ init() {} }).enable(); // forces PromiseHooks to be enabled.
Promise.resolve(1729).then(() => {
console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`);
});
// produces:
// eid 7 tid 6CommonJS
const { createHook, executionAsyncId, triggerAsyncId } = require('node:async_hooks');
createHook({ init() {} }).enable(); // forces PromiseHooks to be enabled.
Promise.resolve(1729).then(() => {
console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`);
});
// produces:
// eid 7 tid 6В этом примере добавление любой фактической функции хука включило отслеживание промисов. В приведённом выше примере есть два промиса: промис, созданный вызовом Promise.resolve(), и промис, возвращённый вызовом then(). В примере выше первому промису был назначен asyncId 6, а последнему — asyncId 7. Во время выполнения callback-функции then() мы выполняемся в контексте промиса с asyncId 7. Этот промис был инициирован асинхронным ресурсом 6.
Ещё одна особенность промисов заключается в том, что callback-функции before и after выполняются только для связанных цепочкой промисов. Это означает, что для промисов, созданных не с помощью then()/catch(), callback-функции before и after не будут вызваны. Дополнительные сведения см. в описании API V8 PromiseHooks.
API встраивания JavaScript
Разработчики библиотек, управляющие собственными асинхронными ресурсами для выполнения таких задач, как ввод-вывод, объединение соединений или управление очередями callback-функций, могут использовать API JavaScript AsyncResource, чтобы вызывались все необходимые callback-функции.
Класс: AsyncResource
Документация этого класса перемещена AsyncResource.
Класс: AsyncLocalStorage
Документация этого класса перемещена AsyncLocalStorage.
© Joyent, Inc. and other Node contributors
Licensed under the MIT License.
Node.js is a trademark of Joyent, Inc. and is used with its permission.
We are not endorsed by or affiliated with Joyent.
https://nodejs.org/dist/latest-v20.x/docs/api/async_hooks.html