Spec-Zone.ru › Node.js 24 LTS

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

Стабильность: 1 — Экспериментальный API. По возможности перейдите на другой API. Мы не рекомендуем использовать API createHook, AsyncHook и executionAsyncResource, поскольку у них есть проблемы с удобством использования, риски для безопасности и влияние на производительность. Для отслеживания асинхронного контекста лучше подходит стабильный API AsyncLocalStorage. Если у вас есть сценарий использования createHook, AsyncHook или executionAsyncResource, выходящий за рамки отслеживания контекста, решаемого с помощью AsyncLocalStorage, или диагностических данных, предоставляемых в настоящее время каналом диагностики, создайте 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');

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

Асинхронный ресурс представляет собой объект, связанный с callback-функцией. Эта 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(options)

Добавлено в: v8.1.0
  • options <Object> Callback-функции хуков для регистрации: обратные вызовы хуков
    • init <Function> Callback-функция init.
    • before <Function> Callback-функция before.
    • after <Function> Callback-функция after.
    • destroy <Function> Callback-функция destroy.
    • promiseResolve <Function> Callback-функция promiseResolve.
    • trackPromises <boolean> Нужно ли хуку отслеживать объекты Promise. Не может иметь значение false, если задано promiseResolve. По умолчанию: true.
  • Возвращает: <AsyncHook> Экземпляр, используемый для отключения и включения хуков

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

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

Все callback-функции необязательны. Например, если нужно отслеживать только очистку ресурсов, достаточно передать callback-функцию 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) { },
});

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; в этом случае будет выведена трассировка стека, приложение завершит работу, а также будет создан файл core.

Причина такого поведения при обработке ошибок заключается в том, что эти 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-функции, а также уничтожение экземпляра.

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

Вызывается при создании класса, который может генерировать асинхронное событие. Это не означает, что экземпляр должен вызвать 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 и запланированной ими асинхронной работы. Объекты Promise отслеживаются только тогда, когда параметр trackPromises имеет значение true.

Пользователи публичного 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 вызывается непосредственно перед выполнением этой callback-функции. 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)
Добавлено в: v8.6.0
  • asyncId <number>

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

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

На этом этапе Promise не обязательно находится в выполненном или отклоненном состоянии, если 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()

Добавлено в: v13.9.0, v12.17.0
  • Возвращает: <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()

История изменений
Версия Изменения
v8.2.0

Переименовано из currentId.

v8.1.0

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

  • Возвращает: <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

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

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

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

По умолчанию для выполнений промисов не назначаются значения asyncId из-за относительно высокой стоимости предоставляемого V8 API для анализа промисов. Это означает, что программы, использующие промисы или 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 0
CommonJS
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 6
CommonJS
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.

Отключение отслеживания выполнения промисов

Отслеживание выполнения промисов может значительно снизить производительность. Чтобы отказаться от отслеживания промисов, установите для trackPromises значение false:

CommonJS
const { createHook } = require('node:async_hooks');
const { writeSync } = require('node:fs');
createHook({
  init(asyncId, type, triggerAsyncId, resource) {
    // This init hook does not get called when trackPromises is set to false.
    writeSync(1, `init hook triggered for ${type}\n`);
  },
  trackPromises: false,  // Do not track promises.
}).enable();
Promise.resolve(1729);
Модули JavaScript
import { createHook } from 'node:async_hooks';
import { writeSync } from 'node:fs';

createHook({
  init(asyncId, type, triggerAsyncId, resource) {
    // This init hook does not get called when trackPromises is set to false.
    writeSync(1, `init hook triggered for ${type}\n`);
  },
  trackPromises: false,  // Do not track promises.
}).enable();
Promise.resolve(1729);

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-v24.x/docs/api/async_hooks.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API