• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Async hooks
2
3<!--introduced_in=v8.1.0-->
4
5> Stability: 1 - Experimental. Please migrate away from this API, if you can.
6> We do not recommend using the [`createHook`][], [`AsyncHook`][], and
7> [`executionAsyncResource`][] APIs as they have usability issues, safety risks,
8> and performance implications. Async context tracking use cases are better
9> served by the stable [`AsyncLocalStorage`][] API. If you have a use case for
10> `createHook`, `AsyncHook`, or `executionAsyncResource` beyond the context
11> tracking need solved by [`AsyncLocalStorage`][] or diagnostics data currently
12> provided by [Diagnostics Channel][], please open an issue at
13> <https://github.com/nodejs/node/issues> describing your use case so we can
14> create a more purpose-focused API.
15
16<!-- source_link=lib/async_hooks.js -->
17
18We strongly discourage the use of the `async_hooks` API.
19Other APIs that can cover most of its use cases include:
20
21* [`AsyncLocalStorage`][] tracks async context
22* [`process.getActiveResourcesInfo()`][] tracks active resources
23
24The `node:async_hooks` module provides an API to track asynchronous resources.
25It can be accessed using:
26
27```mjs
28import async_hooks from 'node:async_hooks';
29```
30
31```cjs
32const async_hooks = require('node:async_hooks');
33```
34
35## Terminology
36
37An asynchronous resource represents an object with an associated callback.
38This callback may be called multiple times, such as the `'connection'`
39event in `net.createServer()`, or just a single time like in `fs.open()`.
40A resource can also be closed before the callback is called. `AsyncHook` does
41not explicitly distinguish between these different cases but will represent them
42as the abstract concept that is a resource.
43
44If [`Worker`][]s are used, each thread has an independent `async_hooks`
45interface, and each thread will use a new set of async IDs.
46
47## Overview
48
49Following is a simple overview of the public API.
50
51```mjs
52import async_hooks from 'node:async_hooks';
53
54// Return the ID of the current execution context.
55const eid = async_hooks.executionAsyncId();
56
57// Return the ID of the handle responsible for triggering the callback of the
58// current execution scope to call.
59const tid = async_hooks.triggerAsyncId();
60
61// Create a new AsyncHook instance. All of these callbacks are optional.
62const asyncHook =
63    async_hooks.createHook({ init, before, after, destroy, promiseResolve });
64
65// Allow callbacks of this AsyncHook instance to call. This is not an implicit
66// action after running the constructor, and must be explicitly run to begin
67// executing callbacks.
68asyncHook.enable();
69
70// Disable listening for new asynchronous events.
71asyncHook.disable();
72
73//
74// The following are the callbacks that can be passed to createHook().
75//
76
77// init() is called during object construction. The resource may not have
78// completed construction when this callback runs. Therefore, all fields of the
79// resource referenced by "asyncId" may not have been populated.
80function init(asyncId, type, triggerAsyncId, resource) { }
81
82// before() is called just before the resource's callback is called. It can be
83// called 0-N times for handles (such as TCPWrap), and will be called exactly 1
84// time for requests (such as FSReqCallback).
85function before(asyncId) { }
86
87// after() is called just after the resource's callback has finished.
88function after(asyncId) { }
89
90// destroy() is called when the resource is destroyed.
91function destroy(asyncId) { }
92
93// promiseResolve() is called only for promise resources, when the
94// resolve() function passed to the Promise constructor is invoked
95// (either directly or through other means of resolving a promise).
96function promiseResolve(asyncId) { }
97```
98
99```cjs
100const async_hooks = require('node:async_hooks');
101
102// Return the ID of the current execution context.
103const eid = async_hooks.executionAsyncId();
104
105// Return the ID of the handle responsible for triggering the callback of the
106// current execution scope to call.
107const tid = async_hooks.triggerAsyncId();
108
109// Create a new AsyncHook instance. All of these callbacks are optional.
110const asyncHook =
111    async_hooks.createHook({ init, before, after, destroy, promiseResolve });
112
113// Allow callbacks of this AsyncHook instance to call. This is not an implicit
114// action after running the constructor, and must be explicitly run to begin
115// executing callbacks.
116asyncHook.enable();
117
118// Disable listening for new asynchronous events.
119asyncHook.disable();
120
121//
122// The following are the callbacks that can be passed to createHook().
123//
124
125// init() is called during object construction. The resource may not have
126// completed construction when this callback runs. Therefore, all fields of the
127// resource referenced by "asyncId" may not have been populated.
128function init(asyncId, type, triggerAsyncId, resource) { }
129
130// before() is called just before the resource's callback is called. It can be
131// called 0-N times for handles (such as TCPWrap), and will be called exactly 1
132// time for requests (such as FSReqCallback).
133function before(asyncId) { }
134
135// after() is called just after the resource's callback has finished.
136function after(asyncId) { }
137
138// destroy() is called when the resource is destroyed.
139function destroy(asyncId) { }
140
141// promiseResolve() is called only for promise resources, when the
142// resolve() function passed to the Promise constructor is invoked
143// (either directly or through other means of resolving a promise).
144function promiseResolve(asyncId) { }
145```
146
147## `async_hooks.createHook(callbacks)`
148
149<!-- YAML
150added: v8.1.0
151-->
152
153* `callbacks` {Object} The [Hook Callbacks][] to register
154  * `init` {Function} The [`init` callback][].
155  * `before` {Function} The [`before` callback][].
156  * `after` {Function} The [`after` callback][].
157  * `destroy` {Function} The [`destroy` callback][].
158  * `promiseResolve` {Function} The [`promiseResolve` callback][].
159* Returns: {AsyncHook} Instance used for disabling and enabling hooks
160
161Registers functions to be called for different lifetime events of each async
162operation.
163
164The callbacks `init()`/`before()`/`after()`/`destroy()` are called for the
165respective asynchronous event during a resource's lifetime.
166
167All callbacks are optional. For example, if only resource cleanup needs to
168be tracked, then only the `destroy` callback needs to be passed. The
169specifics of all functions that can be passed to `callbacks` is in the
170[Hook Callbacks][] section.
171
172```mjs
173import { createHook } from 'node:async_hooks';
174
175const asyncHook = createHook({
176  init(asyncId, type, triggerAsyncId, resource) { },
177  destroy(asyncId) { },
178});
179```
180
181```cjs
182const async_hooks = require('node:async_hooks');
183
184const asyncHook = async_hooks.createHook({
185  init(asyncId, type, triggerAsyncId, resource) { },
186  destroy(asyncId) { },
187});
188```
189
190The callbacks will be inherited via the prototype chain:
191
192```js
193class MyAsyncCallbacks {
194  init(asyncId, type, triggerAsyncId, resource) { }
195  destroy(asyncId) {}
196}
197
198class MyAddedCallbacks extends MyAsyncCallbacks {
199  before(asyncId) { }
200  after(asyncId) { }
201}
202
203const asyncHook = async_hooks.createHook(new MyAddedCallbacks());
204```
205
206Because promises are asynchronous resources whose lifecycle is tracked
207via the async hooks mechanism, the `init()`, `before()`, `after()`, and
208`destroy()` callbacks _must not_ be async functions that return promises.
209
210### Error handling
211
212If any `AsyncHook` callbacks throw, the application will print the stack trace
213and exit. The exit path does follow that of an uncaught exception, but
214all `'uncaughtException'` listeners are removed, thus forcing the process to
215exit. The `'exit'` callbacks will still be called unless the application is run
216with `--abort-on-uncaught-exception`, in which case a stack trace will be
217printed and the application exits, leaving a core file.
218
219The reason for this error handling behavior is that these callbacks are running
220at potentially volatile points in an object's lifetime, for example during
221class construction and destruction. Because of this, it is deemed necessary to
222bring down the process quickly in order to prevent an unintentional abort in the
223future. This is subject to change in the future if a comprehensive analysis is
224performed to ensure an exception can follow the normal control flow without
225unintentional side effects.
226
227### Printing in `AsyncHook` callbacks
228
229Because printing to the console is an asynchronous operation, `console.log()`
230will cause `AsyncHook` callbacks to be called. Using `console.log()` or
231similar asynchronous operations inside an `AsyncHook` callback function will
232cause an infinite recursion. An easy solution to this when debugging is to use a
233synchronous logging operation such as `fs.writeFileSync(file, msg, flag)`.
234This will print to the file and will not invoke `AsyncHook` recursively because
235it is synchronous.
236
237```mjs
238import { writeFileSync } from 'node:fs';
239import { format } from 'node:util';
240
241function debug(...args) {
242  // Use a function like this one when debugging inside an AsyncHook callback
243  writeFileSync('log.out', `${format(...args)}\n`, { flag: 'a' });
244}
245```
246
247```cjs
248const fs = require('node:fs');
249const util = require('node:util');
250
251function debug(...args) {
252  // Use a function like this one when debugging inside an AsyncHook callback
253  fs.writeFileSync('log.out', `${util.format(...args)}\n`, { flag: 'a' });
254}
255```
256
257If an asynchronous operation is needed for logging, it is possible to keep
258track of what caused the asynchronous operation using the information
259provided by `AsyncHook` itself. The logging should then be skipped when
260it was the logging itself that caused the `AsyncHook` callback to be called. By
261doing this, the otherwise infinite recursion is broken.
262
263## Class: `AsyncHook`
264
265The class `AsyncHook` exposes an interface for tracking lifetime events
266of asynchronous operations.
267
268### `asyncHook.enable()`
269
270* Returns: {AsyncHook} A reference to `asyncHook`.
271
272Enable the callbacks for a given `AsyncHook` instance. If no callbacks are
273provided, enabling is a no-op.
274
275The `AsyncHook` instance is disabled by default. If the `AsyncHook` instance
276should be enabled immediately after creation, the following pattern can be used.
277
278```mjs
279import { createHook } from 'node:async_hooks';
280
281const hook = createHook(callbacks).enable();
282```
283
284```cjs
285const async_hooks = require('node:async_hooks');
286
287const hook = async_hooks.createHook(callbacks).enable();
288```
289
290### `asyncHook.disable()`
291
292* Returns: {AsyncHook} A reference to `asyncHook`.
293
294Disable the callbacks for a given `AsyncHook` instance from the global pool of
295`AsyncHook` callbacks to be executed. Once a hook has been disabled it will not
296be called again until enabled.
297
298For API consistency `disable()` also returns the `AsyncHook` instance.
299
300### Hook callbacks
301
302Key events in the lifetime of asynchronous events have been categorized into
303four areas: instantiation, before/after the callback is called, and when the
304instance is destroyed.
305
306#### `init(asyncId, type, triggerAsyncId, resource)`
307
308* `asyncId` {number} A unique ID for the async resource.
309* `type` {string} The type of the async resource.
310* `triggerAsyncId` {number} The unique ID of the async resource in whose
311  execution context this async resource was created.
312* `resource` {Object} Reference to the resource representing the async
313  operation, needs to be released during _destroy_.
314
315Called when a class is constructed that has the _possibility_ to emit an
316asynchronous event. This _does not_ mean the instance must call
317`before`/`after` before `destroy` is called, only that the possibility
318exists.
319
320This behavior can be observed by doing something like opening a resource then
321closing it before the resource can be used. The following snippet demonstrates
322this.
323
324```mjs
325import { createServer } from 'node:net';
326
327createServer().listen(function() { this.close(); });
328// OR
329clearTimeout(setTimeout(() => {}, 10));
330```
331
332```cjs
333require('node:net').createServer().listen(function() { this.close(); });
334// OR
335clearTimeout(setTimeout(() => {}, 10));
336```
337
338Every new resource is assigned an ID that is unique within the scope of the
339current Node.js instance.
340
341##### `type`
342
343The `type` is a string identifying the type of resource that caused
344`init` to be called. Generally, it will correspond to the name of the
345resource's constructor.
346
347The `type` of resources created by Node.js itself can change in any Node.js
348release. Valid values include `TLSWRAP`,
349`TCPWRAP`, `TCPSERVERWRAP`, `GETADDRINFOREQWRAP`, `FSREQCALLBACK`,
350`Microtask`, and `Timeout`. Inspect the source code of the Node.js version used
351to get the full list.
352
353Furthermore users of [`AsyncResource`][] create async resources independent
354of Node.js itself.
355
356There is also the `PROMISE` resource type, which is used to track `Promise`
357instances and asynchronous work scheduled by them.
358
359Users are able to define their own `type` when using the public embedder API.
360
361It is possible to have type name collisions. Embedders are encouraged to use
362unique prefixes, such as the npm package name, to prevent collisions when
363listening to the hooks.
364
365##### `triggerAsyncId`
366
367`triggerAsyncId` is the `asyncId` of the resource that caused (or "triggered")
368the new resource to initialize and that caused `init` to call. This is different
369from `async_hooks.executionAsyncId()` that only shows _when_ a resource was
370created, while `triggerAsyncId` shows _why_ a resource was created.
371
372The following is a simple demonstration of `triggerAsyncId`:
373
374```mjs
375import { createHook, executionAsyncId } from 'node:async_hooks';
376import { stdout } from 'node:process';
377import net from 'node:net';
378import fs from 'node:fs';
379
380createHook({
381  init(asyncId, type, triggerAsyncId) {
382    const eid = executionAsyncId();
383    fs.writeSync(
384      stdout.fd,
385      `${type}(${asyncId}): trigger: ${triggerAsyncId} execution: ${eid}\n`);
386  },
387}).enable();
388
389net.createServer((conn) => {}).listen(8080);
390```
391
392```cjs
393const { createHook, executionAsyncId } = require('node:async_hooks');
394const { stdout } = require('node:process');
395const net = require('node:net');
396const fs = require('node:fs');
397
398createHook({
399  init(asyncId, type, triggerAsyncId) {
400    const eid = executionAsyncId();
401    fs.writeSync(
402      stdout.fd,
403      `${type}(${asyncId}): trigger: ${triggerAsyncId} execution: ${eid}\n`);
404  },
405}).enable();
406
407net.createServer((conn) => {}).listen(8080);
408```
409
410Output when hitting the server with `nc localhost 8080`:
411
412```console
413TCPSERVERWRAP(5): trigger: 1 execution: 1
414TCPWRAP(7): trigger: 5 execution: 0
415```
416
417The `TCPSERVERWRAP` is the server which receives the connections.
418
419The `TCPWRAP` is the new connection from the client. When a new
420connection is made, the `TCPWrap` instance is immediately constructed. This
421happens outside of any JavaScript stack. (An `executionAsyncId()` of `0` means
422that it is being executed from C++ with no JavaScript stack above it.) With only
423that information, it would be impossible to link resources together in
424terms of what caused them to be created, so `triggerAsyncId` is given the task
425of propagating what resource is responsible for the new resource's existence.
426
427##### `resource`
428
429`resource` is an object that represents the actual async resource that has
430been initialized. The API to access the object may be specified by the
431creator of the resource. Resources created by Node.js itself are internal
432and may change at any time. Therefore no API is specified for these.
433
434In some cases the resource object is reused for performance reasons, it is
435thus not safe to use it as a key in a `WeakMap` or add properties to it.
436
437##### Asynchronous context example
438
439The context tracking use case is covered by the stable API [`AsyncLocalStorage`][].
440This example only illustrates async hooks operation but [`AsyncLocalStorage`][]
441fits better to this use case.
442
443The following is an example with additional information about the calls to
444`init` between the `before` and `after` calls, specifically what the
445callback to `listen()` will look like. The output formatting is slightly more
446elaborate to make calling context easier to see.
447
448```mjs
449import async_hooks from 'node:async_hooks';
450import fs from 'node:fs';
451import net from 'node:net';
452import { stdout } from 'node:process';
453const { fd } = stdout;
454
455let indent = 0;
456async_hooks.createHook({
457  init(asyncId, type, triggerAsyncId) {
458    const eid = async_hooks.executionAsyncId();
459    const indentStr = ' '.repeat(indent);
460    fs.writeSync(
461      fd,
462      `${indentStr}${type}(${asyncId}):` +
463      ` trigger: ${triggerAsyncId} execution: ${eid}\n`);
464  },
465  before(asyncId) {
466    const indentStr = ' '.repeat(indent);
467    fs.writeSync(fd, `${indentStr}before:  ${asyncId}\n`);
468    indent += 2;
469  },
470  after(asyncId) {
471    indent -= 2;
472    const indentStr = ' '.repeat(indent);
473    fs.writeSync(fd, `${indentStr}after:  ${asyncId}\n`);
474  },
475  destroy(asyncId) {
476    const indentStr = ' '.repeat(indent);
477    fs.writeSync(fd, `${indentStr}destroy:  ${asyncId}\n`);
478  },
479}).enable();
480
481net.createServer(() => {}).listen(8080, () => {
482  // Let's wait 10ms before logging the server started.
483  setTimeout(() => {
484    console.log('>>>', async_hooks.executionAsyncId());
485  }, 10);
486});
487```
488
489```cjs
490const async_hooks = require('node:async_hooks');
491const fs = require('node:fs');
492const net = require('node:net');
493const { fd } = process.stdout;
494
495let indent = 0;
496async_hooks.createHook({
497  init(asyncId, type, triggerAsyncId) {
498    const eid = async_hooks.executionAsyncId();
499    const indentStr = ' '.repeat(indent);
500    fs.writeSync(
501      fd,
502      `${indentStr}${type}(${asyncId}):` +
503      ` trigger: ${triggerAsyncId} execution: ${eid}\n`);
504  },
505  before(asyncId) {
506    const indentStr = ' '.repeat(indent);
507    fs.writeSync(fd, `${indentStr}before:  ${asyncId}\n`);
508    indent += 2;
509  },
510  after(asyncId) {
511    indent -= 2;
512    const indentStr = ' '.repeat(indent);
513    fs.writeSync(fd, `${indentStr}after:  ${asyncId}\n`);
514  },
515  destroy(asyncId) {
516    const indentStr = ' '.repeat(indent);
517    fs.writeSync(fd, `${indentStr}destroy:  ${asyncId}\n`);
518  },
519}).enable();
520
521net.createServer(() => {}).listen(8080, () => {
522  // Let's wait 10ms before logging the server started.
523  setTimeout(() => {
524    console.log('>>>', async_hooks.executionAsyncId());
525  }, 10);
526});
527```
528
529Output from only starting the server:
530
531```console
532TCPSERVERWRAP(5): trigger: 1 execution: 1
533TickObject(6): trigger: 5 execution: 1
534before:  6
535  Timeout(7): trigger: 6 execution: 6
536after:   6
537destroy: 6
538before:  7
539>>> 7
540  TickObject(8): trigger: 7 execution: 7
541after:   7
542before:  8
543after:   8
544```
545
546As illustrated in the example, `executionAsyncId()` and `execution` each specify
547the value of the current execution context; which is delineated by calls to
548`before` and `after`.
549
550Only using `execution` to graph resource allocation results in the following:
551
552```console
553  root(1)
554     ^
555     |
556TickObject(6)
557     ^
558     |
559 Timeout(7)
560```
561
562The `TCPSERVERWRAP` is not part of this graph, even though it was the reason for
563`console.log()` being called. This is because binding to a port without a host
564name is a _synchronous_ operation, but to maintain a completely asynchronous
565API the user's callback is placed in a `process.nextTick()`. Which is why
566`TickObject` is present in the output and is a 'parent' for `.listen()`
567callback.
568
569The graph only shows _when_ a resource was created, not _why_, so to track
570the _why_ use `triggerAsyncId`. Which can be represented with the following
571graph:
572
573```console
574 bootstrap(1)
575     |
576     ˅
577TCPSERVERWRAP(5)
578     |
579     ˅
580 TickObject(6)
581     |
582     ˅
583  Timeout(7)
584```
585
586#### `before(asyncId)`
587
588* `asyncId` {number}
589
590When an asynchronous operation is initiated (such as a TCP server receiving a
591new connection) or completes (such as writing data to disk) a callback is
592called to notify the user. The `before` callback is called just before said
593callback is executed. `asyncId` is the unique identifier assigned to the
594resource about to execute the callback.
595
596The `before` callback will be called 0 to N times. The `before` callback
597will typically be called 0 times if the asynchronous operation was cancelled
598or, for example, if no connections are received by a TCP server. Persistent
599asynchronous resources like a TCP server will typically call the `before`
600callback multiple times, while other operations like `fs.open()` will call
601it only once.
602
603#### `after(asyncId)`
604
605* `asyncId` {number}
606
607Called immediately after the callback specified in `before` is completed.
608
609If an uncaught exception occurs during execution of the callback, then `after`
610will run _after_ the `'uncaughtException'` event is emitted or a `domain`'s
611handler runs.
612
613#### `destroy(asyncId)`
614
615* `asyncId` {number}
616
617Called after the resource corresponding to `asyncId` is destroyed. It is also
618called asynchronously from the embedder API `emitDestroy()`.
619
620Some resources depend on garbage collection for cleanup, so if a reference is
621made to the `resource` object passed to `init` it is possible that `destroy`
622will never be called, causing a memory leak in the application. If the resource
623does not depend on garbage collection, then this will not be an issue.
624
625Using the destroy hook results in additional overhead because it enables
626tracking of `Promise` instances via the garbage collector.
627
628#### `promiseResolve(asyncId)`
629
630<!-- YAML
631added: v8.6.0
632-->
633
634* `asyncId` {number}
635
636Called when the `resolve` function passed to the `Promise` constructor is
637invoked (either directly or through other means of resolving a promise).
638
639`resolve()` does not do any observable synchronous work.
640
641The `Promise` is not necessarily fulfilled or rejected at this point if the
642`Promise` was resolved by assuming the state of another `Promise`.
643
644```js
645new Promise((resolve) => resolve(true)).then((a) => {});
646```
647
648calls the following callbacks:
649
650```text
651init for PROMISE with id 5, trigger id: 1
652  promise resolve 5      # corresponds to resolve(true)
653init for PROMISE with id 6, trigger id: 5  # the Promise returned by then()
654  before 6               # the then() callback is entered
655  promise resolve 6      # the then() callback resolves the promise by returning
656  after 6
657```
658
659### `async_hooks.executionAsyncResource()`
660
661<!-- YAML
662added:
663 - v13.9.0
664 - v12.17.0
665-->
666
667* Returns: {Object} The resource representing the current execution.
668  Useful to store data within the resource.
669
670Resource objects returned by `executionAsyncResource()` are most often internal
671Node.js handle objects with undocumented APIs. Using any functions or properties
672on the object is likely to crash your application and should be avoided.
673
674Using `executionAsyncResource()` in the top-level execution context will
675return an empty object as there is no handle or request object to use,
676but having an object representing the top-level can be helpful.
677
678```mjs
679import { open } from 'node:fs';
680import { executionAsyncId, executionAsyncResource } from 'node:async_hooks';
681
682console.log(executionAsyncId(), executionAsyncResource());  // 1 {}
683open(new URL(import.meta.url), 'r', (err, fd) => {
684  console.log(executionAsyncId(), executionAsyncResource());  // 7 FSReqWrap
685});
686```
687
688```cjs
689const { open } = require('node:fs');
690const { executionAsyncId, executionAsyncResource } = require('node:async_hooks');
691
692console.log(executionAsyncId(), executionAsyncResource());  // 1 {}
693open(__filename, 'r', (err, fd) => {
694  console.log(executionAsyncId(), executionAsyncResource());  // 7 FSReqWrap
695});
696```
697
698This can be used to implement continuation local storage without the
699use of a tracking `Map` to store the metadata:
700
701```mjs
702import { createServer } from 'node:http';
703import {
704  executionAsyncId,
705  executionAsyncResource,
706  createHook,
707} from 'async_hooks';
708const sym = Symbol('state'); // Private symbol to avoid pollution
709
710createHook({
711  init(asyncId, type, triggerAsyncId, resource) {
712    const cr = executionAsyncResource();
713    if (cr) {
714      resource[sym] = cr[sym];
715    }
716  },
717}).enable();
718
719const server = createServer((req, res) => {
720  executionAsyncResource()[sym] = { state: req.url };
721  setTimeout(function() {
722    res.end(JSON.stringify(executionAsyncResource()[sym]));
723  }, 100);
724}).listen(3000);
725```
726
727```cjs
728const { createServer } = require('node:http');
729const {
730  executionAsyncId,
731  executionAsyncResource,
732  createHook,
733} = require('node:async_hooks');
734const sym = Symbol('state'); // Private symbol to avoid pollution
735
736createHook({
737  init(asyncId, type, triggerAsyncId, resource) {
738    const cr = executionAsyncResource();
739    if (cr) {
740      resource[sym] = cr[sym];
741    }
742  },
743}).enable();
744
745const server = createServer((req, res) => {
746  executionAsyncResource()[sym] = { state: req.url };
747  setTimeout(function() {
748    res.end(JSON.stringify(executionAsyncResource()[sym]));
749  }, 100);
750}).listen(3000);
751```
752
753### `async_hooks.executionAsyncId()`
754
755<!-- YAML
756added: v8.1.0
757changes:
758  - version: v8.2.0
759    pr-url: https://github.com/nodejs/node/pull/13490
760    description: Renamed from `currentId`.
761-->
762
763* Returns: {number} The `asyncId` of the current execution context. Useful to
764  track when something calls.
765
766```mjs
767import { executionAsyncId } from 'node:async_hooks';
768import fs from 'node:fs';
769
770console.log(executionAsyncId());  // 1 - bootstrap
771const path = '.';
772fs.open(path, 'r', (err, fd) => {
773  console.log(executionAsyncId());  // 6 - open()
774});
775```
776
777```cjs
778const async_hooks = require('node:async_hooks');
779const fs = require('node:fs');
780
781console.log(async_hooks.executionAsyncId());  // 1 - bootstrap
782const path = '.';
783fs.open(path, 'r', (err, fd) => {
784  console.log(async_hooks.executionAsyncId());  // 6 - open()
785});
786```
787
788The ID returned from `executionAsyncId()` is related to execution timing, not
789causality (which is covered by `triggerAsyncId()`):
790
791```js
792const server = net.createServer((conn) => {
793  // Returns the ID of the server, not of the new connection, because the
794  // callback runs in the execution scope of the server's MakeCallback().
795  async_hooks.executionAsyncId();
796
797}).listen(port, () => {
798  // Returns the ID of a TickObject (process.nextTick()) because all
799  // callbacks passed to .listen() are wrapped in a nextTick().
800  async_hooks.executionAsyncId();
801});
802```
803
804Promise contexts may not get precise `executionAsyncIds` by default.
805See the section on [promise execution tracking][].
806
807### `async_hooks.triggerAsyncId()`
808
809* Returns: {number} The ID of the resource responsible for calling the callback
810  that is currently being executed.
811
812```js
813const server = net.createServer((conn) => {
814  // The resource that caused (or triggered) this callback to be called
815  // was that of the new connection. Thus the return value of triggerAsyncId()
816  // is the asyncId of "conn".
817  async_hooks.triggerAsyncId();
818
819}).listen(port, () => {
820  // Even though all callbacks passed to .listen() are wrapped in a nextTick()
821  // the callback itself exists because the call to the server's .listen()
822  // was made. So the return value would be the ID of the server.
823  async_hooks.triggerAsyncId();
824});
825```
826
827Promise contexts may not get valid `triggerAsyncId`s by default. See
828the section on [promise execution tracking][].
829
830### `async_hooks.asyncWrapProviders`
831
832<!-- YAML
833added:
834  - v17.2.0
835  - v16.14.0
836-->
837
838* Returns: A map of provider types to the corresponding numeric id.
839  This map contains all the event types that might be emitted by the `async_hooks.init()` event.
840
841This feature suppresses the deprecated usage of `process.binding('async_wrap').Providers`.
842See: [DEP0111][]
843
844## Promise execution tracking
845
846By default, promise executions are not assigned `asyncId`s due to the relatively
847expensive nature of the [promise introspection API][PromiseHooks] provided by
848V8. This means that programs using promises or `async`/`await` will not get
849correct execution and trigger ids for promise callback contexts by default.
850
851```mjs
852import { executionAsyncId, triggerAsyncId } from 'node:async_hooks';
853
854Promise.resolve(1729).then(() => {
855  console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`);
856});
857// produces:
858// eid 1 tid 0
859```
860
861```cjs
862const { executionAsyncId, triggerAsyncId } = require('node:async_hooks');
863
864Promise.resolve(1729).then(() => {
865  console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`);
866});
867// produces:
868// eid 1 tid 0
869```
870
871Observe that the `then()` callback claims to have executed in the context of the
872outer scope even though there was an asynchronous hop involved. Also,
873the `triggerAsyncId` value is `0`, which means that we are missing context about
874the resource that caused (triggered) the `then()` callback to be executed.
875
876Installing async hooks via `async_hooks.createHook` enables promise execution
877tracking:
878
879```mjs
880import { createHook, executionAsyncId, triggerAsyncId } from 'node:async_hooks';
881createHook({ init() {} }).enable(); // forces PromiseHooks to be enabled.
882Promise.resolve(1729).then(() => {
883  console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`);
884});
885// produces:
886// eid 7 tid 6
887```
888
889```cjs
890const { createHook, executionAsyncId, triggerAsyncId } = require('node:async_hooks');
891
892createHook({ init() {} }).enable(); // forces PromiseHooks to be enabled.
893Promise.resolve(1729).then(() => {
894  console.log(`eid ${executionAsyncId()} tid ${triggerAsyncId()}`);
895});
896// produces:
897// eid 7 tid 6
898```
899
900In this example, adding any actual hook function enabled the tracking of
901promises. There are two promises in the example above; the promise created by
902`Promise.resolve()` and the promise returned by the call to `then()`. In the
903example above, the first promise got the `asyncId` `6` and the latter got
904`asyncId` `7`. During the execution of the `then()` callback, we are executing
905in the context of promise with `asyncId` `7`. This promise was triggered by
906async resource `6`.
907
908Another subtlety with promises is that `before` and `after` callbacks are run
909only on chained promises. That means promises not created by `then()`/`catch()`
910will not have the `before` and `after` callbacks fired on them. For more details
911see the details of the V8 [PromiseHooks][] API.
912
913## JavaScript embedder API
914
915Library developers that handle their own asynchronous resources performing tasks
916like I/O, connection pooling, or managing callback queues may use the
917`AsyncResource` JavaScript API so that all the appropriate callbacks are called.
918
919### Class: `AsyncResource`
920
921The documentation for this class has moved [`AsyncResource`][].
922
923## Class: `AsyncLocalStorage`
924
925The documentation for this class has moved [`AsyncLocalStorage`][].
926
927[DEP0111]: deprecations.md#dep0111-processbinding
928[Diagnostics Channel]: diagnostics_channel.md
929[Hook Callbacks]: #hook-callbacks
930[PromiseHooks]: https://docs.google.com/document/d/1rda3yKGHimKIhg5YeoAmCOtyURgsbTH_qaYR79FELlk/edit
931[`AsyncHook`]: #class-asynchook
932[`AsyncLocalStorage`]: async_context.md#class-asynclocalstorage
933[`AsyncResource`]: async_context.md#class-asyncresource
934[`Worker`]: worker_threads.md#class-worker
935[`after` callback]: #afterasyncid
936[`before` callback]: #beforeasyncid
937[`createHook`]: #async_hookscreatehookcallbacks
938[`destroy` callback]: #destroyasyncid
939[`executionAsyncResource`]: #async_hooksexecutionasyncresource
940[`init` callback]: #initasyncid-type-triggerasyncid-resource
941[`process.getActiveResourcesInfo()`]: process.md#processgetactiveresourcesinfo
942[`promiseResolve` callback]: #promiseresolveasyncid
943[promise execution tracking]: #promise-execution-tracking
944