Async Hooks
Модуль async_hooks предоставляет API для регистрации обратных вызовов, отслеживающих жизненный цикл асинхронных ресурсов, созданных внутри приложения Node.js. К нему можно получить доступ с помощью:
const async_hooks = require('async_hooks');
Терминология
Асинхронный ресурс представляет собой объект с ассоциированным обратным вызовом. Этот обратный вызов может вызываться многократно, например, событие connection в net.createServer, или только один раз, как в fs.open. Ресурс также может быть закрыт до вызова обратного вызова. AsyncHook не делает явного различия между этими различными случаями, но будет представлять их как абстрактную концепцию ресурса.
Общедоступный API
Обзор
Ниже приведён простой обзор общедоступного API.
const async_hooks = require('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 (e.g. TCPWrap), and will be called exactly 1
// time for requests (e.g. FSReqWrap).
function before(asyncId) { }
// after is called just after the resource's callback has finished.
function after(asyncId) { }
// destroy is called when an AsyncWrap instance is destroyed.
function destroy(asyncId) { }
// promiseResolve is called only for promise resources, when the
// `resolve` function passed to the `Promise` constructor is invoked
// (either directly or through other means of resolving a promise).
function promiseResolve(asyncId) { }
async_hooks.createHook(callbacks)
-
callbacks<Объект> Обратные вызовы хука для регистрации - Возвращает: <AsyncHook> Экземпляр, используемый для отключения и включения хуков
Регистрирует функции, которые должны вызываться для различных событий жизненного цикла каждой асинхронной операции.
Обратные вызовы init()/before()/after()/destroy() вызываются для соответствующего асинхронного события в течение жизненного цикла ресурса.
Все обратные вызовы являются необязательными. Например, если необходимо отслеживать только очистку ресурса, достаточно передать только обратный вызов destroy. Подробности всех функций, которые можно передать в callbacks, указаны в разделе Обратные вызовы хука.
const async_hooks = require('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());
Обработка ошибок
Если какой-либо из обратных вызовов AsyncHook вызывает исключение, приложение выведет трассировку стека и завершится. Путь завершения соответствует пути обработки необработанного исключения, но все слушатели uncaughtException удаляются, что вынуждает процесс завершиться. Обратные вызовы 'exit' всё ещё будут вызваны, если приложение не запущено с --abort-on-uncaught-exception, в таком случае будет выведена трассировка стека, и приложение завершит работу, сохранив файл core.
Причина такого поведения обработки ошибок заключается в том, что эти обратные вызовы выполняются в потенциально изменчивых точках жизненного цикла объекта, например, во время создания и уничтожения класса. По этой причине считается необходимым быстро завершить процесс, чтобы предотвратить непреднамеренное прерывание в будущем. Это может быть изменено в будущем, если будет проведено всестороннее исследование, чтобы убедиться, что исключение может следовать обычному потоку управления без непреднамеренных побочных эффектов.
Вывод в обратных вызовах AsyncHooks
Поскольку вывод в консоль является асинхронной операцией, console.log() приведет к вызову обратных вызовов AsyncHooks. Использование console.log() или аналогичных асинхронных операций внутри функции обратного вызова AsyncHooks, таким образом, вызовет бесконечную рекурсию. Простое решение при отладке — использование синхронной операции логирования, такой как fs.writeSync(1, msg). Это будет выведено в stdout, так как 1 является дескриптором файла stdout и не вызовет рекурсию AsyncHooks, потому что является синхронной.
const fs = require('fs');
const util = require('util');
function debug(...args) {
// use a function like this one when debugging inside an AsyncHooks callback
fs.writeSync(1, `${util.format(...args)}\n`);
}
Если для логирования требуется асинхронная операция, можно отслеживать причину асинхронной операции, используя информацию, предоставляемую самим AsyncHooks. Затем логирование следует пропустить, когда причиной вызова обратного вызова AsyncHooks было само логирование. Это позволит прервать бесконечную рекурсию.
asyncHook.enable()
- Возвращает: <AsyncHook> Ссылка на
asyncHook.
Включает обратные вызовы для данного экземпляра AsyncHook. Если обратные вызовы не заданы, включение является пустой операцией.
Экземпляр AsyncHook отключён по умолчанию. Если экземпляр AsyncHook должен быть включён сразу после создания, можно использовать следующий шаблон.
const async_hooks = require('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, только что такая возможность существует.
Это поведение можно наблюдать, например, открыв ресурс, а затем закрыв его, прежде чем он будет использован. Следующий фрагмент кода демонстрирует это.
require('net').createServer().listen(function() { this.close(); });
// OR
clearTimeout(setTimeout(() => {}, 10));
Каждый новый ресурс получает уникальный идентификатор в рамках текущего процесса.
type
type — строка, идентифицирующая тип ресурса, вызвавшего вызов init. Обычно он соответствует имени конструктора ресурса.
FSEVENTWRAP, FSREQWRAP, GETADDRINFOREQWRAP, GETNAMEINFOREQWRAP, HTTPPARSER, JSSTREAM, PIPECONNECTWRAP, PIPEWRAP, PROCESSWRAP, QUERYWRAP, SHUTDOWNWRAP, SIGNALWRAP, STATWATCHER, TCPCONNECTWRAP, TCPSERVERWRAP, TCPWRAP, TIMERWRAP, TTYWRAP, UDPSENDWRAP, UDPWRAP, WRITEWRAP, ZLIB, SSLCONNECTION, PBKDF2REQUEST, RANDOMBYTESREQUEST, TLSWRAP, Timeout, Immediate, TickObject
Также существует тип ресурса PROMISE, который используется для отслеживания экземпляров Promise и асинхронной работы, запланированной ими.
Пользователи могут определять свои собственные type при использовании API встраиваемого модуля.
Примечание: Возможны коллизии имён типов. Встраиваемым модулям рекомендуется использовать уникальные префиксы, такие как имя пакета npm, чтобы предотвратить коллизии при прослушивании хуков.
triggerId
triggerAsyncId — это asyncId ресурса, который вызвал инициализацию нового ресурса и вызвав init для вызова. Это отличается от async_hooks.executionAsyncId(), которое показывает когда был создан ресурс, в то время как triggerAsyncId показывает почему был создан ресурс.
Следующий пример демонстрирует triggerAsyncId:
async_hooks.createHook({
init(asyncId, type, triggerAsyncId) {
const eid = async_hooks.executionAsyncId();
fs.writeSync(
1, `${type}(${asyncId}): trigger: ${triggerAsyncId} execution: ${eid}\n`);
}
}).enable();
require('net').createServer((conn) => {}).listen(8080);
Вывод при обращении к серверу с nc localhost 8080:
TCPSERVERWRAP(2): trigger: 1 execution: 1 TCPWRAP(4): trigger: 2 execution: 0
TCPSERVERWRAP — это сервер, который принимает подключения.
TCPWRAP — это новое подключение от клиента. При установлении нового подключения экземпляр TCPWrap немедленно создаётся. Это происходит вне любой стеки JavaScript (примечание: выполнение из executionAsyncId() 0 означает, что оно выполняется из C++, без стеки JavaScript над ним). С этой информацией было бы невозможно связать ресурсы с точки зрения того, что вызвало их создание, поэтому triggerAsyncId получает задачу распространения информации о том, какой ресурс отвечает за существование нового ресурса.
resource
resource — это объект, который представляет фактический асинхронный ресурс, который был инициализирован. Он может содержать полезную информацию, которая может отличаться в зависимости от значения type. Например, для типа ресурса GETADDRINFOREQWRAP resource предоставляет имя хоста, используемого при поиске IP-адреса для имени хоста в net.Server.listen(). API для доступа к этой информации в настоящее время не считается общедоступным, но с помощью API встраиваемого модуля пользователи могут предоставлять и документировать свои собственные объекты ресурсов. Например, такой объект ресурса может содержать выполняемый SQL-запрос.
В случае с Promise объект resource будет иметь свойство promise, которое ссылается на Promise, который инициализируется, и свойство isChainedPromise, установленное в true, если у Promise есть родительский Promise, и false в противном случае. Например, в случае с b = a.then(handler), a считается родительским Promise b. Здесь b считается последовательным Promise.
Примечание: В некоторых случаях объект ресурса повторно используется для повышения производительности, поэтому небезопасно использовать его в качестве ключа в WeakMap или добавлять к нему свойства.
Пример асинхронного контекста
Следующий пример содержит дополнительную информацию о вызовах init между вызовами before и after, а также о том, как будет выглядеть обратный вызов listen(). Форматирование вывода несколько более подробное, чтобы облегчить отображение контекста вызова.
let indent = 0;
async_hooks.createHook({
init(asyncId, type, triggerAsyncId) {
const eid = async_hooks.executionAsyncId();
const indentStr = ' '.repeat(indent);
fs.writeSync(
1,
`${indentStr}${type}(${asyncId}):` +
` trigger: ${triggerAsyncId} execution: ${eid}\n`);
},
before(asyncId) {
const indentStr = ' '.repeat(indent);
fs.writeSync(1, `${indentStr}before: ${asyncId}\n`);
indent += 2;
},
after(asyncId) {
indent -= 2;
const indentStr = ' '.repeat(indent);
fs.writeSync(1, `${indentStr}after: ${asyncId}\n`);
},
destroy(asyncId) {
const indentStr = ' '.repeat(indent);
fs.writeSync(1, `${indentStr}destroy: ${asyncId}\n`);
},
}).enable();
require('net').createServer(() => {}).listen(8080, () => {
// Let's wait 10ms before logging the server started.
setTimeout(() => {
console.log('>>>', async_hooks.executionAsyncId());
}, 10);
});
Вывод только при запуске сервера:
TCPSERVERWRAP(2): trigger: 1 execution: 1
TickObject(3): trigger: 2 execution: 1
before: 3
Timeout(4): trigger: 3 execution: 3
TIMERWRAP(5): trigger: 3 execution: 3
after: 3
destroy: 3
before: 5
before: 4
TTYWRAP(6): trigger: 4 execution: 4
SIGNALWRAP(7): trigger: 4 execution: 4
TTYWRAP(8): trigger: 4 execution: 4
>>> 4
TickObject(9): trigger: 4 execution: 4
after: 4
after: 5
before: 9
after: 9
destroy: 4
destroy: 9
destroy: 5
Примечание: Как показано в примере, executionAsyncId() и execution каждый определяют значение текущего контекста выполнения; это определяется вызовами before и after.
Использование только execution для построения графика распределения ресурсов приводит к следующему:
TTYWRAP(6) -> Timeout(4) -> TIMERWRAP(5) -> TickObject(3) -> root(1)
TCPSERVERWRAP не входит в этот график, хотя он был причиной вызова console.log(). Это связано с тем, что привязка к порту без имени хоста является синхронной операцией, но для поддержания полностью асинхронного API обратный вызов пользователя помещается в process.nextTick().
График показывает только когда ресурс был создан, а не почему, поэтому для отслеживания почему используйте triggerAsyncId.
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 может никогда не быть вызван, что приведёт к утечке памяти в приложении. Если ресурс не зависит от сборки мусора, это не проблема.
promiseResolve(asyncId)
-
asyncId<число>
Вызывается, когда функция resolve , переданная конструктору Promise, вызывается (прямо или другими способами разрешения обещания).
Обратите внимание, что resolve() не выполняет никаких наблюдаемых синхронных работ.
Примечание: Это не обязательно означает, что обещание Promise выполнено или отклонено на данном этапе, если Promise было разрешено, исходя из состояния другого Promise.
Например:
new Promise((resolve) => resolve(true)).then((a) => {});
вызывает следующие обратные вызовы:
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
async_hooks.executionAsyncId()
- Возвращает: <число>
asyncIdтекущего контекста выполнения. Полезно для отслеживания того, когда что-то вызывается.
Например:
const async_hooks = require('async_hooks');
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(function onConnection(conn) {
// Returns the ID of the server, not of the new connection, because the
// onConnection callback runs in the execution scope of the server's
// MakeCallback().
async_hooks.executionAsyncId();
}).listen(port, function onListening() {
// Returns the ID of a TickObject (i.e. process.nextTick()) because all
// callbacks passed to .listen() are wrapped in a nextTick().
async_hooks.executionAsyncId();
});
Обратите внимание, что контексты обещаний по умолчанию могут не получить точные 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();
});
Обратите внимание, что контексты обещаний по умолчанию могут не получить корректные triggerAsyncIds. См. раздел отслеживания выполнения обещаний.
Отслеживание выполнения обещаний
По умолчанию, выполнения обещаний не назначаются asyncId из-за относительно высокой стоимости API просмотра обещаний, предоставляемого V8. Это означает, что программы, использующие обещания или async/await , по умолчанию не получат правильных идентификаторов выполнения и срабатывания для контекстов обратных вызовов обещаний.
Вот пример:
const ah = require('async_hooks');
Promise.resolve(1729).then(() => {
console.log(`eid ${ah.executionAsyncId()} tid ${ah.triggerAsyncId()}`);
});
// produces:
// eid 1 tid 0
Обратите внимание, что обратный вызов then утверждает, что был выполнен в контексте внешнего области, даже если было вовлечено асинхронное перемещение. Также обратите внимание, что значение triggerAsyncId равно 0, что означает, что нам не хватает контекста о ресурсе, который вызвал (сработал) обратный вызов then для выполнения.
Установка асинхронных хуков через async_hooks.createHook включает отслеживание выполнения обещаний. Пример:
const ah = require('async_hooks');
ah.createHook({ init() {} }).enable(); // forces PromiseHooks to be enabled.
Promise.resolve(1729).then(() => {
console.log(`eid ${ah.executionAsyncId()} tid ${ah.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.
API JavaScript встраивающей среды
Разработчики библиотек, которые обрабатывают собственные асинхронные ресурсы, выполняющие задачи, такие как ввод-вывод, пулы соединений или управление очередями обратных вызовов, могут использовать API JavaScript AsyncWrap , чтобы гарантировать, что все необходимые обратные вызовы будут вызваны.
class AsyncResource()
Класс AsyncResource предназначен для расширения асинхронными ресурсами встраивающей среды. С помощью этого пользователи могут легко вызывать жизненный цикл событий собственных ресурсов.
Хук init срабатывает при создании AsyncResource.
Ниже приведён обзор API AsyncResource.
const { AsyncResource, executionAsyncId } = require('async_hooks');
// AsyncResource() is meant to be extended. Instantiating a
// new AsyncResource() also triggers init. If triggerAsyncId is omitted then
// async_hook.executionAsyncId() is used.
const asyncResource = new AsyncResource(
type, { triggerAsyncId: executionAsyncId(), requireManualDestroy: false }
);
// Run a function in the execution context of the resource. This will
// * establish the context of the resource
// * trigger the AsyncHooks before callbacks
// * call the provided function `fn` with the supplied arguments
// * trigger the AsyncHooks after callbacks
// * restore the original execution context
asyncResource.runInAsyncScope(fn, thisArg, ...args);
// Call AsyncHooks destroy callbacks.
asyncResource.emitDestroy();
// Return the unique ID assigned to the AsyncResource instance.
asyncResource.asyncId();
// Return the trigger ID for the AsyncResource instance.
asyncResource.triggerAsyncId();
// Call AsyncHooks before callbacks.
// Deprecated: Use asyncResource.runInAsyncScope instead.
asyncResource.emitBefore();
// Call AsyncHooks after callbacks.
// Deprecated: Use asyncResource.runInAsyncScope instead.
asyncResource.emitAfter();
AsyncResource(type[, options])
-
type<строка> Тип асинхронного события. -
options<Объект>-
triggerAsyncId<число> Идентификатор контекста выполнения, создавшего это асинхронное событие. По умолчанию:executionAsyncId(). -
requireManualDestroy<логическое> Отключает автоматическуюemitDestroyпри сборке мусора объекта. Обычно не нужно устанавливать (даже еслиemitDestroyвызывается вручную), за исключением случаев, когда asyncId ресурса извлекается, и с ним вызываетсяemitDestroyчувствительных API. По умолчанию:false.
-
Пример использования:
class DBQuery extends AsyncResource {
constructor(db) {
super('DBQuery');
this.db = db;
}
getInfo(query, callback) {
this.db.get(query, (err, data) => {
this.runInAsyncScope(callback, null, err, data);
});
}
close() {
this.db = null;
this.emitDestroy();
}
}
asyncResource.runInAsyncScope(fn[, thisArg, ...args])
-
fn<Функция> Функция, которую нужно вызвать в контексте выполнения этого асинхронного ресурса. -
thisArg<любой> Получатель, который будет использоваться для вызова функции. -
...args<любой> Необязательные аргументы для передачи функции.
Вызывает предоставленную функцию с предоставленными аргументами в контексте выполнения асинхронного ресурса. Это позволит установить контекст, запустить обратные вызовы AsyncHooks before, вызвать функцию, запустить обратные вызовы AsyncHooks after и восстановить исходный контекст выполнения.
asyncResource.emitBefore()
asyncResource.runInAsyncScope() вместо неё.- Возвращает: <undefined>
Вызовите все before обратные вызовы, чтобы уведомить о входе в новое контекст асинхронного выполнения. Если выполняются вложенные вызовы emitBefore(), стек asyncId будет отслеживаться и корректно разматываться.
Вызовы before и after должны быть размотаны в том же порядке, в котором они были вызваны. В противном случае произойдёт невосстановимая ошибка, и процесс будет прерван. По этой причине API emitBefore и emitAfter считаются устаревшими. Пожалуйста, используйте runInAsyncScope, так как это обеспечивает гораздо более безопасную альтернативу.
asyncResource.emitAfter()
asyncResource.runInAsyncScope() вместо неё.- Возвращает: <undefined>
Вызовите все after обратные вызовы. Если были выполнены вложенные вызовы emitBefore(), убедитесь, что стек размотан правильно. В противном случае будет выброшено исключение.
Если обратный вызов пользователя генерирует исключение, emitAfter() будет автоматически вызван для всех asyncId в стеке, если ошибка обработана областью или обработчиком 'uncaughtException'.
Вызовы before и after должны быть размотаны в том же порядке, в котором они были вызваны. В противном случае произойдёт невосстановимая ошибка, и процесс будет прерван. По этой причине API emitBefore и emitAfter считаются устаревшими. Пожалуйста, используйте runInAsyncScope, так как это обеспечивает гораздо более безопасную альтернативу.
asyncResource.emitDestroy()
- Возвращает: <undefined>
Вызовите все destroy обработчики. Это должно вызываться только один раз. Если вызов производится более одного раза, будет выброшено исключение. Этот вызов обязательно нужно делать вручную. Если ресурс оставлен для сбора сборщиком мусора, то destroy обработчики никогда не будут вызваны.
asyncResource.asyncId()
- Возвращает: <число> Уникальный
asyncIdидентификатор, назначенный ресурсу.
asyncResource.triggerAsyncId()
- Возвращает: <число> Тот же
triggerAsyncIdидентификатор, который передаётся конструкторуAsyncResource.
© 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-v8.x/docs/api/async_hooks.html