Spec-Zone.ru › Node.js

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

Исходный код: lib/async_hooks.js

Стабильность: 1 — Экспериментальный API. По возможности перейдите на другой 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 отслеживает асинхронный контекст
  • 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(options)

Добавлено в: v8.1.0
  • options <Object> Функции обратного вызова хуков, которые нужно зарегистрировать
    • init <Function> Функция обратного вызова init.
    • before <Function> Функция обратного вызова before.
    • after <Function> Функция обратного вызова after.
    • destroy <Function> Функция обратного вызова destroy.
    • promiseResolve <Function> Функция обратного вызова promiseResolve.
    • trackPromises <boolean> Следует ли отслеживать Promise. Не может иметь значение false, если задано promiseResolve. По умолчанию: true.
  • Возвращает: <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());
jscopy

Поскольку промисы — это асинхронные ресурсы, жизненный цикл которых отслеживается с помощью механизма асинхронных хуков, функции обратного вызова 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> Ссылка на ресурс, представляющий асинхронную операцию; её необходимо освободить во время уничтожения.

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

Пользователи могут определять собственные значения 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
consolecopy

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
consolecopy

Как показано в примере, executionAsyncId() и execution указывают значение текущего контекста выполнения; его границы определяются вызовами before и after.

Если использовать только execution для построения графа выделения ресурсов, получится следующее:

  root(1)
     ^
     |
TickObject(6)
     ^
     |
 Timeout(7)
consolecopy

TCPSERVERWRAP не входит в этот граф, хотя именно он стал причиной вызова console.log(). Это происходит потому, что привязка к порту без имени хоста — синхронная операция, но для сохранения полностью асинхронного API функция обратного вызова пользователя помещается в process.nextTick(). Поэтому TickObject присутствует в выводе и является «родителем» функции обратного вызова .listen().

Граф показывает только, когда был создан ресурс, но не почему. Для отслеживания причины используйте triggerAsyncId. Её можно представить следующим графом:

 bootstrap(1)
     |
     ˅
TCPSERVERWRAP(5)
     |
     ˅
 TickObject(6)
     |
     ˅
  Timeout(7)
consolecopy
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)
Добавлено в: v8.6.0
  • asyncId <number>

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

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

На этом этапе Promise не обязательно находится в состоянии выполненного или отклонённого промиса, если Promise был разрешён путём принятия состояния другого Promise.

new Promise((resolve) => resolve(true)).then((a) => {});
jscopy

вызывает следующие функции обратного вызова:

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
textcopy

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.1.0
История
Версия Изменения
v8.2.0

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

  • Возвращает: <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();
});
jscopy

По умолчанию контекстам промисов могут не назначаться точные значения 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();
});
jscopy

По умолчанию контекстам промисов могут не назначаться допустимые значения triggerAsyncId. См. раздел об отслеживании выполнения промисов.

async_hooks.asyncWrapProviders

Добавлено в: v17.2.0, v16.14.0
  • Возвращает: карту типов провайдеров и соответствующих им числовых идентификаторов. Эта карта содержит все типы событий, которые могут быть сгенерированы событием 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 0
CommonJS
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 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. Во время выполнения функции обратного вызова then() мы выполняемся в контексте промиса со значением asyncId 7. Этот промис был инициирован асинхронным ресурсом 6.

Ещё одна особенность промисов заключается в том, что функции обратного вызова before и after выполняются только для промисов в цепочке. Это означает, что для промисов, созданных не с помощью then()/catch(), функции обратного вызова 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 для встраивания

Разработчики библиотек, управляющие собственными асинхронными ресурсами для выполнения таких задач, как ввод-вывод, объединение подключений в пул или управление очередями функций обратного вызова, могут использовать 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/api/async_hooks.html

Spec-Zone.ru

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