Асинхронные хуки
createHook, AsyncHook и executionAsyncResource, поскольку у них есть проблемы с удобством использования, риски для безопасности и последствия для производительности. Для отслеживания асинхронного контекста лучше использовать стабильный API AsyncLocalStorage. Если у вас есть вариант использования createHook, AsyncHook или executionAsyncResource, который выходит за рамки отслеживания контекста, решаемого с помощью AsyncLocalStorage, или данных диагностики, предоставляемых в настоящее время каналом Diagnostics Channel, создайте issue по адресу 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');Терминология
Асинхронный ресурс — это объект, которому соответствует обратный вызов. Этот обратный вызов может быть вызван несколько раз, например для события 'connection' в net.createServer(), или только один раз, как в случае с fs.open(). Ресурс также может быть закрыт до вызова обратного вызова. 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> Обратные вызовы хуков, которые нужно зарегистрировать: Обратные вызовы хуков-
init<Function> Обратный вызовinit. -
before<Function> Обратный вызовbefore. -
after<Function> Обратный вызовafter. -
destroy<Function> Обратный вызовdestroy. -
promiseResolve<Function> Обратный вызовpromiseResolve.
-
- Возвращает: <AsyncHook> Экземпляр, используемый для отключения и включения хуков
Регистрирует функции, которые вызываются при различных событиях жизненного цикла каждой асинхронной операции.
Обратные вызовы init()/before()/after()/destroy() вызываются при соответствующих асинхронных событиях в течение жизненного цикла ресурса.
Все обратные вызовы необязательны. Например, если необходимо отслеживать только очистку ресурсов, достаточно передать обратный вызов destroy. Подробное описание всех функций, которые можно передать в 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) { },
});Обратные вызовы наследуются через цепочку прототипов:
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 Поскольку промисы являются асинхронными ресурсами, жизненный цикл которых отслеживается механизмом асинхронных хуков, обратные вызовы 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 рекурсивно, поскольку является синхронной.
Модули 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. В этом случае журналирование следует пропускать, если именно оно вызвало обратный вызов AsyncHook. Это позволяет прервать бесконечную рекурсию.
Класс: AsyncHook
Класс AsyncHook предоставляет интерфейс для отслеживания событий жизненного цикла асинхронных операций.
asyncHook.enable()
- Возвращает: <AsyncHook> Ссылка на
asyncHook.
Включает обратные вызовы для заданного экземпляра AsyncHook. Если обратные вызовы не заданы, включение ничего не делает.
По умолчанию экземпляр 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.
Отключает обратные вызовы для заданного экземпляра AsyncHook в глобальном пуле обратных вызовов AsyncHook, предназначенных для выполнения. После отключения хук не будет вызываться до тех пор, пока его снова не включат.
Для согласованности API disable() также возвращает экземпляр AsyncHook.
Обратные вызовы хуков
Ключевые события жизненного цикла асинхронных событий разделены на четыре категории: создание экземпляра, выполнение обратного вызова и события до и после него, а также уничтожение экземпляра.
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 и запланированной ими асинхронной работы.
Пользователи могут задавать собственные type при использовании открытого API для встраиваемых приложений.
Имена типов могут совпадать. Разработчикам встраиваемых приложений рекомендуется использовать уникальные префиксы, например имя пакета 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, в частности о том, как будет выглядеть обратный вызов для 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 обратный вызов пользователя помещается в process.nextTick(). Поэтому TickObject присутствует в выводе и является «родителем» обратного вызова .listen().
Граф показывает только, когда был создан ресурс, но не почему. Для отслеживания причины используйте triggerAsyncId. Это можно представить следующим графом:
bootstrap(1)
|
˅
TCPSERVERWRAP(5)
|
˅
TickObject(6)
|
˅
Timeout(7) copy
before(asyncId)
-
asyncId<number>
Когда начинается асинхронная операция (например, TCP-сервер получает новое подключение) или завершается (например, данные записываются на диск), для уведомления пользователя вызывается обратный вызов. Обратный вызов before вызывается непосредственно перед выполнением этого обратного вызова. asyncId — это уникальный идентификатор ресурса, обратный вызов которого будет выполнен.
Обратный вызов before может быть вызван от 0 до N раз. Обратный вызов before обычно не вызывается, если асинхронная операция была отменена или, например, если TCP-сервер не получил ни одного подключения. Для постоянных асинхронных ресурсов, таких как TCP-сервер, обратный вызов before обычно вызывается несколько раз, тогда как для других операций, например fs.open(), он вызывается только один раз.
after(asyncId)
-
asyncId<number>
Вызывается сразу после завершения обратного вызова, заданного в before.
Если во время выполнения обратного вызова возникает необработанное исключение, after будет запущен после генерации события 'uncaughtException' или выполнения обработчика domain.
destroy(asyncId)
-
asyncId<number>
Вызывается после уничтожения ресурса, соответствующего asyncId. Также асинхронно вызывается из API для встраиваемых приложений emitDestroy().
Для очистки некоторых ресурсов используется сборка мусора. Поэтому, если сохранить ссылку на объект resource, переданный в init, возможно, что destroy никогда не будет вызван, что приведёт к утечке памяти в приложении. Если ресурс не зависит от сборки мусора, эта проблема не возникнет.
Использование хука уничтожения влечёт дополнительные издержки, поскольку включает отслеживание экземпляров Promise с помощью сборщика мусора.
promiseResolve(asyncId)
-
asyncId<number>
Вызывается, когда функция resolve, переданная конструктору Promise, вызывается напрямую или через другие способы разрешения промиса.
resolve() не выполняет никаких наблюдаемых синхронных действий.
В этот момент Promise не обязательно находится в состоянии выполненного или отклонённого, если Promise был разрешён с принятием состояния другого Promise.
new Promise((resolve) => resolve(true)).then((a) => {}); copy вызывает следующие обратные вызовы:
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> Идентификатор ресурса, ответственного за вызов выполняемого в данный момент обратного вызова.
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, по умолчанию не будут получать корректные идентификаторы выполнения и инициирующих ресурсов для контекстов обратных вызовов промисов.
Модули 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Обратите внимание: обратный вызов then() утверждает, что выполнялся в контексте внешней области видимости, хотя между вызовами был асинхронный переход. Кроме того, значение triggerAsyncId равно 0, то есть нам не хватает контекста о ресурсе, который вызвал (инициировал) выполнение обратного вызова 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. Во время выполнения обратного вызова then() мы находимся в контексте промиса с asyncId 7. Этот промис был инициирован асинхронным ресурсом 6.
Ещё одна особенность промисов заключается в том, что обратные вызовы before и after выполняются только для связанных промисов. Это означает, что у промисов, созданных не с помощью then()/catch(), обратные вызовы before и after не будут вызваны. Подробнее см. описание API PromiseHooks в V8.
API JavaScript для встраиваемых приложений
Разработчики библиотек, работающих с собственными асинхронными ресурсами для выполнения таких задач, как ввод-вывод, объединение подключений в пулы или управление очередями обратных вызовов, могут использовать API JavaScript AsyncResource, чтобы вызывались все необходимые обратные вызовы.
Класс: 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-v22.x/docs/api/async_hooks.html