Spec-Zone.ru › Node.js 18 LTS

Async hooks

Устойчивость: 1 - Экспериментальная. Пожалуйста, перейдите к другому API, если это возможно. Мы не рекомендуем использовать API createHook, AsyncHook и executionAsyncResource, так как они имеют проблемы с удобством использования, риски безопасности и последствия для производительности. Случаи использования отслеживания контекста асинхронных задач лучше решаются с помощью стабильного API AsyncLocalStorage. Если у вас есть случаи использования для createHook, AsyncHook, или executionAsyncResource помимо потребностей в отслеживании контекста, решаемых с помощью AsyncLocalStorage, или диагностических данных, предоставляемых в настоящее время каналом диагностики Канал диагностики, откройте вопрос на https://github.com/nodejs/node/issues, описав свой случай использования, чтобы мы могли создать более целенаправленный API.

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

Мы настоятельно не рекомендуем использовать API async_hooks. Другие API, которые могут охватить большинство его случаев использования, включают:

  • AsyncLocalStorage отслеживает асинхронный контекст
  • process.getActiveResourcesInfo() отслеживает активные ресурсы

Модуль node:async_hooks предоставляет API для отслеживания асинхронных ресурсов. К нему можно получить доступ, используя:

МОДУЛИ MJS

import async_hooks from 'node:async_hooks';

МОДУЛИ CJS

const async_hooks = require('node:async_hooks');

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

Асинхронный ресурс представляет собой объект с ассоциированным обратным вызовом. Этот обратный вызов может вызываться несколько раз, например, событие 'connection' в net.createServer(), или только один раз, как в fs.open(). Ресурс также может быть закрыт до вызова обратного вызова. AsyncHook не явно различает эти разные случаи, но будет представлять их как абстрактное понятие — ресурс.

Если используются Worker, каждый поток имеет отдельный интерфейс async_hooks, и каждый поток будет использовать новый набор идентификаторов асинхронных операций.

Обзор

Ниже приведен простой обзор публичного API.

МОДУЛИ MJS

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) { }

МОДУЛИ CJS

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)

Добавлен в: v8.1.0
  • callbacks <Объект> Зарегистрировать Обратные вызовы хуков
    • init <Функция> Обратный вызов init.
    • before <Функция> Обратный вызов before.
    • after <Функция> Обратный вызов after.
    • destroy <Функция> Обратный вызов destroy.
    • promiseResolve <Функция> Обратный вызов promiseResolve.
  • Возвращает: <AsyncHook> Экземпляр, используемый для отключения и включения хуков

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

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

Все обратные вызовы необязательны. Например, если необходимо отслеживать только очистку ресурсов, то необходимо передать только обратный вызов destroy. Подробности всех функций, которые можно передать в callbacks, находятся в разделе Обратные вызовы хуков.

МОДУЛИ MJS

import { createHook } from 'node:async_hooks';

const asyncHook = createHook({
  init(asyncId, type, triggerAsyncId, resource) { },
  destroy(asyncId) { },
});

МОДУЛИ CJS

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

Поскольку промисы являются асинхронными ресурсами, жизненный цикл которых отслеживается с помощью механизма async hooks, обратные вызовы init(), before(), after(), и destroy() не должны быть асинхронными функциями, возвращающими промисы.

Обработка ошибок

Если любой из обратных вызовов AsyncHook вызывает ошибку, приложение выведет трассировку стека и завершится. Трассировка стека будет выведена, как при необработанном исключении, но все слушатели 'uncaughtException' будут удалены, заставляя процесс завершиться. Обратные вызовы 'exit' по-прежнему будут вызваны, если приложение не запущено с --abort-on-uncaught-exception, в этом случае будет выведена трассировка стека, и приложение завершится, сохранив файл кода.

Причина такой обработки ошибок заключается в том, что эти обратные вызовы выполняются в потенциально нестабильных точках в течение жизненного цикла объекта, например, во время создания и уничтожения класса. Поэтому считается необходимым быстро завершить процесс, чтобы предотвратить непреднамеренный сбой в будущем. Это может быть изменено в будущем, если будет проведен всесторонний анализ, чтобы убедиться, что исключение может следовать нормальному потоку управления без непреднамеренных побочных эффектов.

Вывод в обратных вызовах AsyncHook

Поскольку вывод в консоль является асинхронной операцией, console.log() приведет к вызову обратных вызовов AsyncHook. Использование console.log() или аналогичных асинхронных операций внутри функции обратного вызова AsyncHook приведет к бесконечной рекурсии. Простым решением при отладке является использование синхронной операции записи в журнал, например fs.writeFileSync(file, msg, flag). Это запишет в файл и не вызовет AsyncHook рекурсивно, потому что является синхронной операцией.

МОДУЛИ MJS

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' });
}

МОДУЛИ CJS

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

Модули MJS

import { createHook } from 'node:async_hooks';

const hook = createHook(callbacks).enable();

Модули CJS

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 <число> Уникальный идентификатор асинхронного ресурса.
  • type <строка> Тип асинхронного ресурса.
  • triggerAsyncId <число> Уникальный идентификатор асинхронного ресурса, в контексте выполнения которого был создан данный асинхронный ресурс.
  • resource <объект> Ссылка на ресурс, представляющий асинхронную операцию, необходимо освободить во время destroy.

Вызывается при создании класса, который может генерировать асинхронное событие. Это не означает, что экземпляр должен вызывать before/after перед вызовом destroy, только что такая возможность существует.

Это поведение можно наблюдать, например, открыв ресурс, а затем закрыв его, прежде чем ресурс может быть использован. Следующий фрагмент демонстрирует это.

Модули MJS

import { createServer } from 'node:net';

createServer().listen(function() { this.close(); });
// OR
clearTimeout(setTimeout(() => {}, 10));

Модули CJS

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 показывает почему был создан ресурс.

Следующий пример демонстрирует triggerAsyncId:

Модули MJS

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);

Модули CJS

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(). Формат вывода немного более подробный, чтобы упростить понимание контекста вызова.

Модули MJS

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);
});

Модули CJS

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 <число>

При инициализации асинхронной операции (например, сервер TCP получает новое подключение) или её завершении (например, запись данных на диск) вызывается обработчик для уведомления пользователя. Обработчик before вызывается непосредственно перед выполнением указанного обработчика. asyncId — это уникальный идентификатор, присвоенный ресурсу, который собирается выполнить обработчик.

Обработчик before будет вызываться 0–N раз. Обработчик before обычно вызывается 0 раз, если асинхронная операция была отменена или, например, сервер TCP не получает подключений. Персистентные асинхронные ресурсы, такие как сервер TCP, обычно вызывают обработчик before несколько раз, в то время как другие операции, такие как fs.open(), вызывают его только один раз.

after(asyncId)
  • asyncId <число>

Вызывается сразу после завершения обработчика, указанного в before.

Если во время выполнения обработчика произойдёт непредвиденная ошибка, after будет выполнен после события 'uncaughtException' или выполнения обработчика domain.

destroy(asyncId)
  • asyncId <число>

Вызывается после уничтожения ресурса, соответствующего asyncId. Он также вызывается асинхронно из API встраиваемой среды emitDestroy().

Некоторые ресурсы зависят от сборки мусора для очистки, поэтому, если имеется ссылка на объект resource, переданный в init , возможно, destroy никогда не будет вызван, что приведёт к утечке памяти в приложении. Если ресурс не зависит от сборки мусора, то это не проблема.

Использование обработчика уничтожения приводит к дополнительной нагрузке, так как он позволяет отслеживать экземпляры Promise через сборщик мусора.

promiseResolve(asyncId)
Добавлен в: v8.6.0
  • asyncId <число>

Вызывается, когда функция 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()

Добавлен в: v13.9.0, v12.17.0
  • Возвращает: <Объект> Ресурс, представляющий текущее выполнение. Полезно для хранения данных внутри ресурса.

Объекты ресурсов, возвращаемые executionAsyncResource() , чаще всего являются внутренними объектами обработчика Node.js с недокументированными API. Использование любых функций или свойств объекта, вероятно, приведёт к сбою вашего приложения и должно быть избегаемо.

Использование executionAsyncResource() в контексте выполнения верхнего уровня вернёт пустой объект, так как нет объекта обработчика или запроса для использования, но наличие объекта, представляющего верхний уровень, может быть полезно.

МОДУЛИ MJS

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
});

МОДУЛИ CJS

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 для хранения метаданных:

МОДУЛИ MJS

import { createServer } from 'node:http';
import {
  executionAsyncId,
  executionAsyncResource,
  createHook,
} from '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);

МОДУЛИ CJS

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

  • Возвращает: <число> Идентификатор текущего контекста выполнения. Полезно для отслеживания, когда что-то вызывается.

МОДУЛИ MJS

import { executionAsyncId } from 'node:async_hooks';
import fs from 'node:fs';

console.log(executionAsyncId());  // 1 - bootstrap
fs.open(path, 'r', (err, fd) => {
  console.log(executionAsyncId());  // 6 - open()
});

МОДУЛИ CJS

const async_hooks = require('node:async_hooks');
const fs = require('node:fs');

console.log(async_hooks.executionAsyncId());  // 1 - bootstrap
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()

  • Возвращает: <число> Идентификатор ресурса, ответственного за вызов обратного вызова, который в настоящее время выполняется.
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 , по умолчанию не получат правильные идентификаторы выполнения и срабатывания для контекстов обратного вызова обещаний.

МОДУЛИ MJS

import { executionAsyncId, triggerAsyncId } from 'node:async_hooks';

Promise.resolve(1729).then(() => {
  console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`);
});
// produces:
// eid 1 tid 0

МОДУЛИ CJS

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 включает отслеживание выполнения обещаний:

МОДУЛИ MJS

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

МОДУЛИ CJS

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 . Более подробную информацию см. в документации по V8 PromiseHooks.

JavaScript API для встраивания

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

Spec-Zone.ru

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