Async hooks#
createHook
,AsyncHook
, andexecutionAsyncResource
APIs as they have usability issues, safety risks,and performance implications. Async context tracking use cases are betterserved by the stableAsyncLocalStorage
API. If you have a use case forcreateHook
,AsyncHook
, orexecutionAsyncResource
beyond the contexttracking need solved byAsyncLocalStorage
or diagnostics data currentlyprovided byDiagnostics Channel, please open an issue athttps://github.com/nodejs/node/issues describing your use case so we cancreate a more purpose-focused API.Source Code:lib/async_hooks.js
We strongly discourage the use of theasync_hooks
API.Other APIs that can cover most of its use cases include:
AsyncLocalStorage
tracks async contextprocess.getActiveResourcesInfo()
tracks active resources
Thenode:async_hooks
module provides an API to track asynchronous resources.It can be accessed using:
import async_hooksfrom'node:async_hooks';
const async_hooks =require('node:async_hooks');
Terminology#
An asynchronous resource represents an object with an associated callback.This callback may be called multiple times, such as the'connection'
event innet.createServer()
, or just a single time like infs.open()
.A resource can also be closed before the callback is called.AsyncHook
doesnot explicitly distinguish between these different cases but will represent themas the abstract concept that is a resource.
IfWorker
s are used, each thread has an independentasync_hooks
interface, and each thread will use a new set of async IDs.
Overview#
Following is a simple overview of the public API.
import async_hooksfrom'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.functioninit(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).functionbefore(asyncId) { }// after() is called just after the resource's callback has finished.functionafter(asyncId) { }// destroy() is called when the resource is destroyed.functiondestroy(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).functionpromiseResolve(asyncId) { }
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.functioninit(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).functionbefore(asyncId) { }// after() is called just after the resource's callback has finished.functionafter(asyncId) { }// destroy() is called when the resource is destroyed.functiondestroy(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).functionpromiseResolve(asyncId) { }
async_hooks.createHook(callbacks)
#
callbacks
<Object> TheHook Callbacks to registerinit
<Function> Theinit
callback.before
<Function> Thebefore
callback.after
<Function> Theafter
callback.destroy
<Function> Thedestroy
callback.promiseResolve
<Function> ThepromiseResolve
callback.
- Returns:<AsyncHook> Instance used for disabling and enabling hooks
Registers functions to be called for different lifetime events of each asyncoperation.
The callbacksinit()
/before()
/after()
/destroy()
are called for therespective asynchronous event during a resource's lifetime.
All callbacks are optional. For example, if only resource cleanup needs tobe tracked, then only thedestroy
callback needs to be passed. Thespecifics of all functions that can be passed tocallbacks
is in theHook Callbacks section.
import { createHook }from'node:async_hooks';const asyncHook =createHook({init(asyncId, type, triggerAsyncId, resource) { },destroy(asyncId) { },});
const async_hooks =require('node:async_hooks');const asyncHook = async_hooks.createHook({init(asyncId, type, triggerAsyncId, resource) { },destroy(asyncId) { },});
The callbacks will be inherited via the prototype chain:
classMyAsyncCallbacks {init(asyncId, type, triggerAsyncId, resource) { }destroy(asyncId) {}}classMyAddedCallbacksextendsMyAsyncCallbacks {before(asyncId) { }after(asyncId) { }}const asyncHook = async_hooks.createHook(newMyAddedCallbacks());
Because promises are asynchronous resources whose lifecycle is trackedvia the async hooks mechanism, theinit()
,before()
,after()
, anddestroy()
callbacksmust not be async functions that return promises.
Error handling#
If anyAsyncHook
callbacks throw, the application will print the stack traceand exit. The exit path does follow that of an uncaught exception, butall'uncaughtException'
listeners are removed, thus forcing the process toexit. The'exit'
callbacks will still be called unless the application is runwith--abort-on-uncaught-exception
, in which case a stack trace will beprinted and the application exits, leaving a core file.
The reason for this error handling behavior is that these callbacks are runningat potentially volatile points in an object's lifetime, for example duringclass construction and destruction. Because of this, it is deemed necessary tobring down the process quickly in order to prevent an unintentional abort in thefuture. This is subject to change in the future if a comprehensive analysis isperformed to ensure an exception can follow the normal control flow withoutunintentional side effects.
Printing inAsyncHook
callbacks#
Because printing to the console is an asynchronous operation,console.log()
will causeAsyncHook
callbacks to be called. Usingconsole.log()
orsimilar asynchronous operations inside anAsyncHook
callback function willcause an infinite recursion. An easy solution to this when debugging is to use asynchronous logging operation such asfs.writeFileSync(file, msg, flag)
.This will print to the file and will not invokeAsyncHook
recursively becauseit is synchronous.
import { writeFileSync }from'node:fs';import { format }from'node:util';functiondebug(...args) {// Use a function like this one when debugging inside an AsyncHook callbackwriteFileSync('log.out',`${format(...args)}\n`, {flag:'a' });}
const fs =require('node:fs');const util =require('node:util');functiondebug(...args) {// Use a function like this one when debugging inside an AsyncHook callback fs.writeFileSync('log.out',`${util.format(...args)}\n`, {flag:'a' });}
If an asynchronous operation is needed for logging, it is possible to keeptrack of what caused the asynchronous operation using the informationprovided byAsyncHook
itself. The logging should then be skipped whenit was the logging itself that caused theAsyncHook
callback to be called. Bydoing this, the otherwise infinite recursion is broken.
Class:AsyncHook
#
The classAsyncHook
exposes an interface for tracking lifetime eventsof asynchronous operations.
asyncHook.enable()
#
- Returns:<AsyncHook> A reference to
asyncHook
.
Enable the callbacks for a givenAsyncHook
instance. If no callbacks areprovided, enabling is a no-op.
TheAsyncHook
instance is disabled by default. If theAsyncHook
instanceshould be enabled immediately after creation, the following pattern can be used.
import { createHook }from'node:async_hooks';const hook =createHook(callbacks).enable();
const async_hooks =require('node:async_hooks');const hook = async_hooks.createHook(callbacks).enable();
asyncHook.disable()
#
- Returns:<AsyncHook> A reference to
asyncHook
.
Disable the callbacks for a givenAsyncHook
instance from the global pool ofAsyncHook
callbacks to be executed. Once a hook has been disabled it will notbe called again until enabled.
For API consistencydisable()
also returns theAsyncHook
instance.
Hook callbacks#
Key events in the lifetime of asynchronous events have been categorized intofour areas: instantiation, before/after the callback is called, and when theinstance is destroyed.
init(asyncId, type, triggerAsyncId, resource)
#
asyncId
<number> A unique ID for the async resource.type
<string> The type of the async resource.triggerAsyncId
<number> The unique ID of the async resource in whoseexecution context this async resource was created.resource
<Object> Reference to the resource representing the asyncoperation, needs to be released duringdestroy.
Called when a class is constructed that has thepossibility to emit anasynchronous event. Thisdoes not mean the instance must callbefore
/after
beforedestroy
is called, only that the possibilityexists.
This behavior can be observed by doing something like opening a resource thenclosing it before the resource can be used. The following snippet demonstratesthis.
import { createServer }from'node:net';createServer().listen(function() {this.close(); });// ORclearTimeout(setTimeout(() => {},10));
require('node:net').createServer().listen(function() {this.close(); });// ORclearTimeout(setTimeout(() => {},10));
Every new resource is assigned an ID that is unique within the scope of thecurrent Node.js instance.
type
#
Thetype
is a string identifying the type of resource that causedinit
to be called. Generally, it will correspond to the name of theresource's constructor.
Thetype
of resources created by Node.js itself can change in any Node.jsrelease. Valid values includeTLSWRAP
,TCPWRAP
,TCPSERVERWRAP
,GETADDRINFOREQWRAP
,FSREQCALLBACK
,Microtask
, andTimeout
. Inspect the source code of the Node.js version usedto get the full list.
Furthermore users ofAsyncResource
create async resources independentof Node.js itself.
There is also thePROMISE
resource type, which is used to trackPromise
instances and asynchronous work scheduled by them.
Users are able to define their owntype
when using the public embedder API.
It is possible to have type name collisions. Embedders are encouraged to useunique prefixes, such as the npm package name, to prevent collisions whenlistening to the hooks.
triggerAsyncId
#
triggerAsyncId
is theasyncId
of the resource that caused (or "triggered")the new resource to initialize and that causedinit
to call. This is differentfromasync_hooks.executionAsyncId()
that only showswhen a resource wascreated, whiletriggerAsyncId
showswhy a resource was created.
The following is a simple demonstration oftriggerAsyncId
:
import { createHook, executionAsyncId }from'node:async_hooks';import { stdout }from'node:process';import netfrom'node:net';import fsfrom'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);
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);
Output when hitting the server withnc localhost 8080
:
TCPSERVERWRAP(5): trigger: 1 execution: 1TCPWRAP(7): trigger: 5 execution: 0
TheTCPSERVERWRAP
is the server which receives the connections.
TheTCPWRAP
is the new connection from the client. When a newconnection is made, theTCPWrap
instance is immediately constructed. Thishappens outside of any JavaScript stack. (AnexecutionAsyncId()
of0
meansthat it is being executed from C++ with no JavaScript stack above it.) With onlythat information, it would be impossible to link resources together interms of what caused them to be created, sotriggerAsyncId
is given the taskof propagating what resource is responsible for the new resource's existence.
resource
#
resource
is an object that represents the actual async resource that hasbeen initialized. The API to access the object may be specified by thecreator of the resource. Resources created by Node.js itself are internaland may change at any time. Therefore no API is specified for these.
In some cases the resource object is reused for performance reasons, it isthus not safe to use it as a key in aWeakMap
or add properties to it.
Asynchronous context example#
The context tracking use case is covered by the stable APIAsyncLocalStorage
.This example only illustrates async hooks operation butAsyncLocalStorage
fits better to this use case.
The following is an example with additional information about the calls toinit
between thebefore
andafter
calls, specifically what thecallback tolisten()
will look like. The output formatting is slightly moreelaborate to make calling context easier to see.
import async_hooksfrom'node:async_hooks';import fsfrom'node:fs';import netfrom'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);});
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);});
Output from only starting the server:
TCPSERVERWRAP(5): trigger: 1 execution: 1TickObject(6): trigger: 5 execution: 1before: 6 Timeout(7): trigger: 6 execution: 6after: 6destroy: 6before: 7>>> 7 TickObject(8): trigger: 7 execution: 7after: 7before: 8after: 8
As illustrated in the example,executionAsyncId()
andexecution
each specifythe value of the current execution context; which is delineated by calls tobefore
andafter
.
Only usingexecution
to graph resource allocation results in the following:
root(1) ^ |TickObject(6) ^ | Timeout(7)
TheTCPSERVERWRAP
is not part of this graph, even though it was the reason forconsole.log()
being called. This is because binding to a port without a hostname is asynchronous operation, but to maintain a completely asynchronousAPI the user's callback is placed in aprocess.nextTick()
. Which is whyTickObject
is present in the output and is a 'parent' for.listen()
callback.
The graph only showswhen a resource was created, notwhy, so to trackthewhy usetriggerAsyncId
. Which can be represented with the followinggraph:
bootstrap(1) | ˅TCPSERVERWRAP(5) | ˅ TickObject(6) | ˅ Timeout(7)
before(asyncId)
#
asyncId
<number>
When an asynchronous operation is initiated (such as a TCP server receiving anew connection) or completes (such as writing data to disk) a callback iscalled to notify the user. Thebefore
callback is called just before saidcallback is executed.asyncId
is the unique identifier assigned to theresource about to execute the callback.
Thebefore
callback will be called 0 to N times. Thebefore
callbackwill typically be called 0 times if the asynchronous operation was cancelledor, for example, if no connections are received by a TCP server. Persistentasynchronous resources like a TCP server will typically call thebefore
callback multiple times, while other operations likefs.open()
will callit only once.
after(asyncId)
#
asyncId
<number>
Called immediately after the callback specified inbefore
is completed.
If an uncaught exception occurs during execution of the callback, thenafter
will runafter the'uncaughtException'
event is emitted or adomain
'shandler runs.
destroy(asyncId)
#
asyncId
<number>
Called after the resource corresponding toasyncId
is destroyed. It is alsocalled asynchronously from the embedder APIemitDestroy()
.
Some resources depend on garbage collection for cleanup, so if a reference ismade to theresource
object passed toinit
it is possible thatdestroy
will never be called, causing a memory leak in the application. If the resourcedoes not depend on garbage collection, then this will not be an issue.
Using the destroy hook results in additional overhead because it enablestracking ofPromise
instances via the garbage collector.
promiseResolve(asyncId)
#
asyncId
<number>
Called when theresolve
function passed to thePromise
constructor isinvoked (either directly or through other means of resolving a promise).
resolve()
does not do any observable synchronous work.
ThePromise
is not necessarily fulfilled or rejected at this point if thePromise
was resolved by assuming the state of anotherPromise
.
newPromise((resolve) =>resolve(true)).then((a) => {});
calls the following callbacks:
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.executionAsyncResource()
#
- Returns:<Object> The resource representing the current execution.Useful to store data within the resource.
Resource objects returned byexecutionAsyncResource()
are most often internalNode.js handle objects with undocumented APIs. Using any functions or propertieson the object is likely to crash your application and should be avoided.
UsingexecutionAsyncResource()
in the top-level execution context willreturn an empty object as there is no handle or request object to use,but having an object representing the top-level can be helpful.
import { open }from'node:fs';import { executionAsyncId, executionAsyncResource }from'node:async_hooks';console.log(executionAsyncId(),executionAsyncResource());// 1 {}open(newURL(import.meta.url),'r',(err, fd) => {console.log(executionAsyncId(),executionAsyncResource());// 7 FSReqWrap});
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});
This can be used to implement continuation local storage without theuse of a trackingMap
to store the metadata:
import { createServer }from'node:http';import { executionAsyncId, executionAsyncResource, createHook,}from'node:async_hooks';const sym =Symbol('state');// Private symbol to avoid pollutioncreateHook({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);
const { createServer } =require('node:http');const { executionAsyncId, executionAsyncResource, createHook,} =require('node:async_hooks');const sym =Symbol('state');// Private symbol to avoid pollutioncreateHook({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()
#
History
Version | Changes |
---|---|
v8.2.0 | Renamed from |
v8.1.0 | Added in: v8.1.0 |
- Returns:<number> The
asyncId
of the current execution context. Useful totrack when something calls.
import { executionAsyncId }from'node:async_hooks';import fsfrom'node:fs';console.log(executionAsyncId());// 1 - bootstrapconst path ='.';fs.open(path,'r',(err, fd) => {console.log(executionAsyncId());// 6 - open()});
const async_hooks =require('node:async_hooks');const fs =require('node:fs');console.log(async_hooks.executionAsyncId());// 1 - bootstrapconst path ='.';fs.open(path,'r',(err, fd) => {console.log(async_hooks.executionAsyncId());// 6 - open()});
The ID returned fromexecutionAsyncId()
is related to execution timing, notcausality (which is covered bytriggerAsyncId()
):
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();});
Promise contexts may not get preciseexecutionAsyncIds
by default.See the section onpromise execution tracking.
async_hooks.triggerAsyncId()
#
- Returns:<number> The ID of the resource responsible for calling the callbackthat is currently being executed.
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();});
Promise contexts may not get validtriggerAsyncId
s by default. Seethe section onpromise execution tracking.
async_hooks.asyncWrapProviders
#
- Returns: A map of provider types to the corresponding numeric id.This map contains all the event types that might be emitted by the
async_hooks.init()
event.
This feature suppresses the deprecated usage ofprocess.binding('async_wrap').Providers
.See:DEP0111
Promise execution tracking#
By default, promise executions are not assignedasyncId
s due to the relativelyexpensive nature of thepromise introspection API provided byV8. This means that programs using promises orasync
/await
will not getcorrect execution and trigger ids for promise callback contexts by default.
import { executionAsyncId, triggerAsyncId }from'node:async_hooks';Promise.resolve(1729).then(() => {console.log(`eid${executionAsyncId()} tid${triggerAsyncId()}`);});// produces:// eid 1 tid 0
const { executionAsyncId, triggerAsyncId } =require('node:async_hooks');Promise.resolve(1729).then(() => {console.log(`eid${executionAsyncId()} tid${triggerAsyncId()}`);});// produces:// eid 1 tid 0
Observe that thethen()
callback claims to have executed in the context of theouter scope even though there was an asynchronous hop involved. Also,thetriggerAsyncId
value is0
, which means that we are missing context aboutthe resource that caused (triggered) thethen()
callback to be executed.
Installing async hooks viaasync_hooks.createHook
enables promise executiontracking:
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
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
In this example, adding any actual hook function enabled the tracking ofpromises. There are two promises in the example above; the promise created byPromise.resolve()
and the promise returned by the call tothen()
. In theexample above, the first promise got theasyncId
6
and the latter gotasyncId
7
. During the execution of thethen()
callback, we are executingin the context of promise withasyncId
7
. This promise was triggered byasync resource6
.
Another subtlety with promises is thatbefore
andafter
callbacks are runonly on chained promises. That means promises not created bythen()
/catch()
will not have thebefore
andafter
callbacks fired on them. For more detailssee the details of the V8PromiseHooks API.
JavaScript embedder API#
Library developers that handle their own asynchronous resources performing taskslike I/O, connection pooling, or managing callback queues may use theAsyncResource
JavaScript API so that all the appropriate callbacks are called.
Class:AsyncResource
#
The documentation for this class has movedAsyncResource
.
Class:AsyncLocalStorage
#
The documentation for this class has movedAsyncLocalStorage
.