xref: /third_party/node/doc/api/async_context.md (revision 1cb0ef41)
11cb0ef41Sopenharmony_ci# Asynchronous context tracking
21cb0ef41Sopenharmony_ci
31cb0ef41Sopenharmony_ci<!--introduced_in=v16.4.0-->
41cb0ef41Sopenharmony_ci
51cb0ef41Sopenharmony_ci> Stability: 2 - Stable
61cb0ef41Sopenharmony_ci
71cb0ef41Sopenharmony_ci<!-- source_link=lib/async_hooks.js -->
81cb0ef41Sopenharmony_ci
91cb0ef41Sopenharmony_ci## Introduction
101cb0ef41Sopenharmony_ci
111cb0ef41Sopenharmony_ciThese classes are used to associate state and propagate it throughout
121cb0ef41Sopenharmony_cicallbacks and promise chains.
131cb0ef41Sopenharmony_ciThey allow storing data throughout the lifetime of a web request
141cb0ef41Sopenharmony_cior any other asynchronous duration. It is similar to thread-local storage
151cb0ef41Sopenharmony_ciin other languages.
161cb0ef41Sopenharmony_ci
171cb0ef41Sopenharmony_ciThe `AsyncLocalStorage` and `AsyncResource` classes are part of the
181cb0ef41Sopenharmony_ci`node:async_hooks` module:
191cb0ef41Sopenharmony_ci
201cb0ef41Sopenharmony_ci```mjs
211cb0ef41Sopenharmony_ciimport { AsyncLocalStorage, AsyncResource } from 'node:async_hooks';
221cb0ef41Sopenharmony_ci```
231cb0ef41Sopenharmony_ci
241cb0ef41Sopenharmony_ci```cjs
251cb0ef41Sopenharmony_ciconst { AsyncLocalStorage, AsyncResource } = require('node:async_hooks');
261cb0ef41Sopenharmony_ci```
271cb0ef41Sopenharmony_ci
281cb0ef41Sopenharmony_ci## Class: `AsyncLocalStorage`
291cb0ef41Sopenharmony_ci
301cb0ef41Sopenharmony_ci<!-- YAML
311cb0ef41Sopenharmony_ciadded:
321cb0ef41Sopenharmony_ci - v13.10.0
331cb0ef41Sopenharmony_ci - v12.17.0
341cb0ef41Sopenharmony_cichanges:
351cb0ef41Sopenharmony_ci - version: v16.4.0
361cb0ef41Sopenharmony_ci   pr-url: https://github.com/nodejs/node/pull/37675
371cb0ef41Sopenharmony_ci   description: AsyncLocalStorage is now Stable. Previously, it had been Experimental.
381cb0ef41Sopenharmony_ci-->
391cb0ef41Sopenharmony_ci
401cb0ef41Sopenharmony_ciThis class creates stores that stay coherent through asynchronous operations.
411cb0ef41Sopenharmony_ci
421cb0ef41Sopenharmony_ciWhile you can create your own implementation on top of the `node:async_hooks`
431cb0ef41Sopenharmony_cimodule, `AsyncLocalStorage` should be preferred as it is a performant and memory
441cb0ef41Sopenharmony_cisafe implementation that involves significant optimizations that are non-obvious
451cb0ef41Sopenharmony_cito implement.
461cb0ef41Sopenharmony_ci
471cb0ef41Sopenharmony_ciThe following example uses `AsyncLocalStorage` to build a simple logger
481cb0ef41Sopenharmony_cithat assigns IDs to incoming HTTP requests and includes them in messages
491cb0ef41Sopenharmony_cilogged within each request.
501cb0ef41Sopenharmony_ci
511cb0ef41Sopenharmony_ci```mjs
521cb0ef41Sopenharmony_ciimport http from 'node:http';
531cb0ef41Sopenharmony_ciimport { AsyncLocalStorage } from 'node:async_hooks';
541cb0ef41Sopenharmony_ci
551cb0ef41Sopenharmony_ciconst asyncLocalStorage = new AsyncLocalStorage();
561cb0ef41Sopenharmony_ci
571cb0ef41Sopenharmony_cifunction logWithId(msg) {
581cb0ef41Sopenharmony_ci  const id = asyncLocalStorage.getStore();
591cb0ef41Sopenharmony_ci  console.log(`${id !== undefined ? id : '-'}:`, msg);
601cb0ef41Sopenharmony_ci}
611cb0ef41Sopenharmony_ci
621cb0ef41Sopenharmony_cilet idSeq = 0;
631cb0ef41Sopenharmony_cihttp.createServer((req, res) => {
641cb0ef41Sopenharmony_ci  asyncLocalStorage.run(idSeq++, () => {
651cb0ef41Sopenharmony_ci    logWithId('start');
661cb0ef41Sopenharmony_ci    // Imagine any chain of async operations here
671cb0ef41Sopenharmony_ci    setImmediate(() => {
681cb0ef41Sopenharmony_ci      logWithId('finish');
691cb0ef41Sopenharmony_ci      res.end();
701cb0ef41Sopenharmony_ci    });
711cb0ef41Sopenharmony_ci  });
721cb0ef41Sopenharmony_ci}).listen(8080);
731cb0ef41Sopenharmony_ci
741cb0ef41Sopenharmony_cihttp.get('http://localhost:8080');
751cb0ef41Sopenharmony_cihttp.get('http://localhost:8080');
761cb0ef41Sopenharmony_ci// Prints:
771cb0ef41Sopenharmony_ci//   0: start
781cb0ef41Sopenharmony_ci//   1: start
791cb0ef41Sopenharmony_ci//   0: finish
801cb0ef41Sopenharmony_ci//   1: finish
811cb0ef41Sopenharmony_ci```
821cb0ef41Sopenharmony_ci
831cb0ef41Sopenharmony_ci```cjs
841cb0ef41Sopenharmony_ciconst http = require('node:http');
851cb0ef41Sopenharmony_ciconst { AsyncLocalStorage } = require('node:async_hooks');
861cb0ef41Sopenharmony_ci
871cb0ef41Sopenharmony_ciconst asyncLocalStorage = new AsyncLocalStorage();
881cb0ef41Sopenharmony_ci
891cb0ef41Sopenharmony_cifunction logWithId(msg) {
901cb0ef41Sopenharmony_ci  const id = asyncLocalStorage.getStore();
911cb0ef41Sopenharmony_ci  console.log(`${id !== undefined ? id : '-'}:`, msg);
921cb0ef41Sopenharmony_ci}
931cb0ef41Sopenharmony_ci
941cb0ef41Sopenharmony_cilet idSeq = 0;
951cb0ef41Sopenharmony_cihttp.createServer((req, res) => {
961cb0ef41Sopenharmony_ci  asyncLocalStorage.run(idSeq++, () => {
971cb0ef41Sopenharmony_ci    logWithId('start');
981cb0ef41Sopenharmony_ci    // Imagine any chain of async operations here
991cb0ef41Sopenharmony_ci    setImmediate(() => {
1001cb0ef41Sopenharmony_ci      logWithId('finish');
1011cb0ef41Sopenharmony_ci      res.end();
1021cb0ef41Sopenharmony_ci    });
1031cb0ef41Sopenharmony_ci  });
1041cb0ef41Sopenharmony_ci}).listen(8080);
1051cb0ef41Sopenharmony_ci
1061cb0ef41Sopenharmony_cihttp.get('http://localhost:8080');
1071cb0ef41Sopenharmony_cihttp.get('http://localhost:8080');
1081cb0ef41Sopenharmony_ci// Prints:
1091cb0ef41Sopenharmony_ci//   0: start
1101cb0ef41Sopenharmony_ci//   1: start
1111cb0ef41Sopenharmony_ci//   0: finish
1121cb0ef41Sopenharmony_ci//   1: finish
1131cb0ef41Sopenharmony_ci```
1141cb0ef41Sopenharmony_ci
1151cb0ef41Sopenharmony_ciEach instance of `AsyncLocalStorage` maintains an independent storage context.
1161cb0ef41Sopenharmony_ciMultiple instances can safely exist simultaneously without risk of interfering
1171cb0ef41Sopenharmony_ciwith each other's data.
1181cb0ef41Sopenharmony_ci
1191cb0ef41Sopenharmony_ci### `new AsyncLocalStorage()`
1201cb0ef41Sopenharmony_ci
1211cb0ef41Sopenharmony_ci<!-- YAML
1221cb0ef41Sopenharmony_ciadded:
1231cb0ef41Sopenharmony_ci - v13.10.0
1241cb0ef41Sopenharmony_ci - v12.17.0
1251cb0ef41Sopenharmony_cichanges:
1261cb0ef41Sopenharmony_ci - version: v18.16.0
1271cb0ef41Sopenharmony_ci   pr-url: https://github.com/nodejs/node/pull/46386
1281cb0ef41Sopenharmony_ci   description: Removed experimental onPropagate option.
1291cb0ef41Sopenharmony_ci - version: v18.13.0
1301cb0ef41Sopenharmony_ci   pr-url: https://github.com/nodejs/node/pull/45386
1311cb0ef41Sopenharmony_ci   description: Add option onPropagate.
1321cb0ef41Sopenharmony_ci-->
1331cb0ef41Sopenharmony_ci
1341cb0ef41Sopenharmony_ciCreates a new instance of `AsyncLocalStorage`. Store is only provided within a
1351cb0ef41Sopenharmony_ci`run()` call or after an `enterWith()` call.
1361cb0ef41Sopenharmony_ci
1371cb0ef41Sopenharmony_ci### Static method: `AsyncLocalStorage.bind(fn)`
1381cb0ef41Sopenharmony_ci
1391cb0ef41Sopenharmony_ci<!-- YAML
1401cb0ef41Sopenharmony_ciadded: v18.16.0
1411cb0ef41Sopenharmony_ci-->
1421cb0ef41Sopenharmony_ci
1431cb0ef41Sopenharmony_ci> Stability: 1 - Experimental
1441cb0ef41Sopenharmony_ci
1451cb0ef41Sopenharmony_ci* `fn` {Function} The function to bind to the current execution context.
1461cb0ef41Sopenharmony_ci* Returns: {Function} A new function that calls `fn` within the captured
1471cb0ef41Sopenharmony_ci  execution context.
1481cb0ef41Sopenharmony_ci
1491cb0ef41Sopenharmony_ciBinds the given function to the current execution context.
1501cb0ef41Sopenharmony_ci
1511cb0ef41Sopenharmony_ci### Static method: `AsyncLocalStorage.snapshot()`
1521cb0ef41Sopenharmony_ci
1531cb0ef41Sopenharmony_ci<!-- YAML
1541cb0ef41Sopenharmony_ciadded: v18.16.0
1551cb0ef41Sopenharmony_ci-->
1561cb0ef41Sopenharmony_ci
1571cb0ef41Sopenharmony_ci> Stability: 1 - Experimental
1581cb0ef41Sopenharmony_ci
1591cb0ef41Sopenharmony_ci* Returns: {Function} A new function with the signature
1601cb0ef41Sopenharmony_ci  `(fn: (...args) : R, ...args) : R`.
1611cb0ef41Sopenharmony_ci
1621cb0ef41Sopenharmony_ciCaptures the current execution context and returns a function that accepts a
1631cb0ef41Sopenharmony_cifunction as an argument. Whenever the returned function is called, it
1641cb0ef41Sopenharmony_cicalls the function passed to it within the captured context.
1651cb0ef41Sopenharmony_ci
1661cb0ef41Sopenharmony_ci```js
1671cb0ef41Sopenharmony_ciconst asyncLocalStorage = new AsyncLocalStorage();
1681cb0ef41Sopenharmony_ciconst runInAsyncScope = asyncLocalStorage.run(123, () => AsyncLocalStorage.snapshot());
1691cb0ef41Sopenharmony_ciconst result = asyncLocalStorage.run(321, () => runInAsyncScope(() => asyncLocalStorage.getStore()));
1701cb0ef41Sopenharmony_ciconsole.log(result);  // returns 123
1711cb0ef41Sopenharmony_ci```
1721cb0ef41Sopenharmony_ci
1731cb0ef41Sopenharmony_ciAsyncLocalStorage.snapshot() can replace the use of AsyncResource for simple
1741cb0ef41Sopenharmony_ciasync context tracking purposes, for example:
1751cb0ef41Sopenharmony_ci
1761cb0ef41Sopenharmony_ci```js
1771cb0ef41Sopenharmony_ciclass Foo {
1781cb0ef41Sopenharmony_ci  #runInAsyncScope = AsyncLocalStorage.snapshot();
1791cb0ef41Sopenharmony_ci
1801cb0ef41Sopenharmony_ci  get() { return this.#runInAsyncScope(() => asyncLocalStorage.getStore()); }
1811cb0ef41Sopenharmony_ci}
1821cb0ef41Sopenharmony_ci
1831cb0ef41Sopenharmony_ciconst foo = asyncLocalStorage.run(123, () => new Foo());
1841cb0ef41Sopenharmony_ciconsole.log(asyncLocalStorage.run(321, () => foo.get())); // returns 123
1851cb0ef41Sopenharmony_ci```
1861cb0ef41Sopenharmony_ci
1871cb0ef41Sopenharmony_ci### `asyncLocalStorage.disable()`
1881cb0ef41Sopenharmony_ci
1891cb0ef41Sopenharmony_ci<!-- YAML
1901cb0ef41Sopenharmony_ciadded:
1911cb0ef41Sopenharmony_ci - v13.10.0
1921cb0ef41Sopenharmony_ci - v12.17.0
1931cb0ef41Sopenharmony_ci-->
1941cb0ef41Sopenharmony_ci
1951cb0ef41Sopenharmony_ci> Stability: 1 - Experimental
1961cb0ef41Sopenharmony_ci
1971cb0ef41Sopenharmony_ciDisables the instance of `AsyncLocalStorage`. All subsequent calls
1981cb0ef41Sopenharmony_cito `asyncLocalStorage.getStore()` will return `undefined` until
1991cb0ef41Sopenharmony_ci`asyncLocalStorage.run()` or `asyncLocalStorage.enterWith()` is called again.
2001cb0ef41Sopenharmony_ci
2011cb0ef41Sopenharmony_ciWhen calling `asyncLocalStorage.disable()`, all current contexts linked to the
2021cb0ef41Sopenharmony_ciinstance will be exited.
2031cb0ef41Sopenharmony_ci
2041cb0ef41Sopenharmony_ciCalling `asyncLocalStorage.disable()` is required before the
2051cb0ef41Sopenharmony_ci`asyncLocalStorage` can be garbage collected. This does not apply to stores
2061cb0ef41Sopenharmony_ciprovided by the `asyncLocalStorage`, as those objects are garbage collected
2071cb0ef41Sopenharmony_cialong with the corresponding async resources.
2081cb0ef41Sopenharmony_ci
2091cb0ef41Sopenharmony_ciUse this method when the `asyncLocalStorage` is not in use anymore
2101cb0ef41Sopenharmony_ciin the current process.
2111cb0ef41Sopenharmony_ci
2121cb0ef41Sopenharmony_ci### `asyncLocalStorage.getStore()`
2131cb0ef41Sopenharmony_ci
2141cb0ef41Sopenharmony_ci<!-- YAML
2151cb0ef41Sopenharmony_ciadded:
2161cb0ef41Sopenharmony_ci - v13.10.0
2171cb0ef41Sopenharmony_ci - v12.17.0
2181cb0ef41Sopenharmony_ci-->
2191cb0ef41Sopenharmony_ci
2201cb0ef41Sopenharmony_ci* Returns: {any}
2211cb0ef41Sopenharmony_ci
2221cb0ef41Sopenharmony_ciReturns the current store.
2231cb0ef41Sopenharmony_ciIf called outside of an asynchronous context initialized by
2241cb0ef41Sopenharmony_cicalling `asyncLocalStorage.run()` or `asyncLocalStorage.enterWith()`, it
2251cb0ef41Sopenharmony_cireturns `undefined`.
2261cb0ef41Sopenharmony_ci
2271cb0ef41Sopenharmony_ci### `asyncLocalStorage.enterWith(store)`
2281cb0ef41Sopenharmony_ci
2291cb0ef41Sopenharmony_ci<!-- YAML
2301cb0ef41Sopenharmony_ciadded:
2311cb0ef41Sopenharmony_ci - v13.11.0
2321cb0ef41Sopenharmony_ci - v12.17.0
2331cb0ef41Sopenharmony_ci-->
2341cb0ef41Sopenharmony_ci
2351cb0ef41Sopenharmony_ci> Stability: 1 - Experimental
2361cb0ef41Sopenharmony_ci
2371cb0ef41Sopenharmony_ci* `store` {any}
2381cb0ef41Sopenharmony_ci
2391cb0ef41Sopenharmony_ciTransitions into the context for the remainder of the current
2401cb0ef41Sopenharmony_cisynchronous execution and then persists the store through any following
2411cb0ef41Sopenharmony_ciasynchronous calls.
2421cb0ef41Sopenharmony_ci
2431cb0ef41Sopenharmony_ciExample:
2441cb0ef41Sopenharmony_ci
2451cb0ef41Sopenharmony_ci```js
2461cb0ef41Sopenharmony_ciconst store = { id: 1 };
2471cb0ef41Sopenharmony_ci// Replaces previous store with the given store object
2481cb0ef41Sopenharmony_ciasyncLocalStorage.enterWith(store);
2491cb0ef41Sopenharmony_ciasyncLocalStorage.getStore(); // Returns the store object
2501cb0ef41Sopenharmony_cisomeAsyncOperation(() => {
2511cb0ef41Sopenharmony_ci  asyncLocalStorage.getStore(); // Returns the same object
2521cb0ef41Sopenharmony_ci});
2531cb0ef41Sopenharmony_ci```
2541cb0ef41Sopenharmony_ci
2551cb0ef41Sopenharmony_ciThis transition will continue for the _entire_ synchronous execution.
2561cb0ef41Sopenharmony_ciThis means that if, for example, the context is entered within an event
2571cb0ef41Sopenharmony_cihandler subsequent event handlers will also run within that context unless
2581cb0ef41Sopenharmony_cispecifically bound to another context with an `AsyncResource`. That is why
2591cb0ef41Sopenharmony_ci`run()` should be preferred over `enterWith()` unless there are strong reasons
2601cb0ef41Sopenharmony_cito use the latter method.
2611cb0ef41Sopenharmony_ci
2621cb0ef41Sopenharmony_ci```js
2631cb0ef41Sopenharmony_ciconst store = { id: 1 };
2641cb0ef41Sopenharmony_ci
2651cb0ef41Sopenharmony_ciemitter.on('my-event', () => {
2661cb0ef41Sopenharmony_ci  asyncLocalStorage.enterWith(store);
2671cb0ef41Sopenharmony_ci});
2681cb0ef41Sopenharmony_ciemitter.on('my-event', () => {
2691cb0ef41Sopenharmony_ci  asyncLocalStorage.getStore(); // Returns the same object
2701cb0ef41Sopenharmony_ci});
2711cb0ef41Sopenharmony_ci
2721cb0ef41Sopenharmony_ciasyncLocalStorage.getStore(); // Returns undefined
2731cb0ef41Sopenharmony_ciemitter.emit('my-event');
2741cb0ef41Sopenharmony_ciasyncLocalStorage.getStore(); // Returns the same object
2751cb0ef41Sopenharmony_ci```
2761cb0ef41Sopenharmony_ci
2771cb0ef41Sopenharmony_ci### `asyncLocalStorage.run(store, callback[, ...args])`
2781cb0ef41Sopenharmony_ci
2791cb0ef41Sopenharmony_ci<!-- YAML
2801cb0ef41Sopenharmony_ciadded:
2811cb0ef41Sopenharmony_ci - v13.10.0
2821cb0ef41Sopenharmony_ci - v12.17.0
2831cb0ef41Sopenharmony_ci-->
2841cb0ef41Sopenharmony_ci
2851cb0ef41Sopenharmony_ci* `store` {any}
2861cb0ef41Sopenharmony_ci* `callback` {Function}
2871cb0ef41Sopenharmony_ci* `...args` {any}
2881cb0ef41Sopenharmony_ci
2891cb0ef41Sopenharmony_ciRuns a function synchronously within a context and returns its
2901cb0ef41Sopenharmony_cireturn value. The store is not accessible outside of the callback function.
2911cb0ef41Sopenharmony_ciThe store is accessible to any asynchronous operations created within the
2921cb0ef41Sopenharmony_cicallback.
2931cb0ef41Sopenharmony_ci
2941cb0ef41Sopenharmony_ciThe optional `args` are passed to the callback function.
2951cb0ef41Sopenharmony_ci
2961cb0ef41Sopenharmony_ciIf the callback function throws an error, the error is thrown by `run()` too.
2971cb0ef41Sopenharmony_ciThe stacktrace is not impacted by this call and the context is exited.
2981cb0ef41Sopenharmony_ci
2991cb0ef41Sopenharmony_ciExample:
3001cb0ef41Sopenharmony_ci
3011cb0ef41Sopenharmony_ci```js
3021cb0ef41Sopenharmony_ciconst store = { id: 2 };
3031cb0ef41Sopenharmony_citry {
3041cb0ef41Sopenharmony_ci  asyncLocalStorage.run(store, () => {
3051cb0ef41Sopenharmony_ci    asyncLocalStorage.getStore(); // Returns the store object
3061cb0ef41Sopenharmony_ci    setTimeout(() => {
3071cb0ef41Sopenharmony_ci      asyncLocalStorage.getStore(); // Returns the store object
3081cb0ef41Sopenharmony_ci    }, 200);
3091cb0ef41Sopenharmony_ci    throw new Error();
3101cb0ef41Sopenharmony_ci  });
3111cb0ef41Sopenharmony_ci} catch (e) {
3121cb0ef41Sopenharmony_ci  asyncLocalStorage.getStore(); // Returns undefined
3131cb0ef41Sopenharmony_ci  // The error will be caught here
3141cb0ef41Sopenharmony_ci}
3151cb0ef41Sopenharmony_ci```
3161cb0ef41Sopenharmony_ci
3171cb0ef41Sopenharmony_ci### `asyncLocalStorage.exit(callback[, ...args])`
3181cb0ef41Sopenharmony_ci
3191cb0ef41Sopenharmony_ci<!-- YAML
3201cb0ef41Sopenharmony_ciadded:
3211cb0ef41Sopenharmony_ci - v13.10.0
3221cb0ef41Sopenharmony_ci - v12.17.0
3231cb0ef41Sopenharmony_ci-->
3241cb0ef41Sopenharmony_ci
3251cb0ef41Sopenharmony_ci> Stability: 1 - Experimental
3261cb0ef41Sopenharmony_ci
3271cb0ef41Sopenharmony_ci* `callback` {Function}
3281cb0ef41Sopenharmony_ci* `...args` {any}
3291cb0ef41Sopenharmony_ci
3301cb0ef41Sopenharmony_ciRuns a function synchronously outside of a context and returns its
3311cb0ef41Sopenharmony_cireturn value. The store is not accessible within the callback function or
3321cb0ef41Sopenharmony_cithe asynchronous operations created within the callback. Any `getStore()`
3331cb0ef41Sopenharmony_cicall done within the callback function will always return `undefined`.
3341cb0ef41Sopenharmony_ci
3351cb0ef41Sopenharmony_ciThe optional `args` are passed to the callback function.
3361cb0ef41Sopenharmony_ci
3371cb0ef41Sopenharmony_ciIf the callback function throws an error, the error is thrown by `exit()` too.
3381cb0ef41Sopenharmony_ciThe stacktrace is not impacted by this call and the context is re-entered.
3391cb0ef41Sopenharmony_ci
3401cb0ef41Sopenharmony_ciExample:
3411cb0ef41Sopenharmony_ci
3421cb0ef41Sopenharmony_ci```js
3431cb0ef41Sopenharmony_ci// Within a call to run
3441cb0ef41Sopenharmony_citry {
3451cb0ef41Sopenharmony_ci  asyncLocalStorage.getStore(); // Returns the store object or value
3461cb0ef41Sopenharmony_ci  asyncLocalStorage.exit(() => {
3471cb0ef41Sopenharmony_ci    asyncLocalStorage.getStore(); // Returns undefined
3481cb0ef41Sopenharmony_ci    throw new Error();
3491cb0ef41Sopenharmony_ci  });
3501cb0ef41Sopenharmony_ci} catch (e) {
3511cb0ef41Sopenharmony_ci  asyncLocalStorage.getStore(); // Returns the same object or value
3521cb0ef41Sopenharmony_ci  // The error will be caught here
3531cb0ef41Sopenharmony_ci}
3541cb0ef41Sopenharmony_ci```
3551cb0ef41Sopenharmony_ci
3561cb0ef41Sopenharmony_ci### Usage with `async/await`
3571cb0ef41Sopenharmony_ci
3581cb0ef41Sopenharmony_ciIf, within an async function, only one `await` call is to run within a context,
3591cb0ef41Sopenharmony_cithe following pattern should be used:
3601cb0ef41Sopenharmony_ci
3611cb0ef41Sopenharmony_ci```js
3621cb0ef41Sopenharmony_ciasync function fn() {
3631cb0ef41Sopenharmony_ci  await asyncLocalStorage.run(new Map(), () => {
3641cb0ef41Sopenharmony_ci    asyncLocalStorage.getStore().set('key', value);
3651cb0ef41Sopenharmony_ci    return foo(); // The return value of foo will be awaited
3661cb0ef41Sopenharmony_ci  });
3671cb0ef41Sopenharmony_ci}
3681cb0ef41Sopenharmony_ci```
3691cb0ef41Sopenharmony_ci
3701cb0ef41Sopenharmony_ciIn this example, the store is only available in the callback function and the
3711cb0ef41Sopenharmony_cifunctions called by `foo`. Outside of `run`, calling `getStore` will return
3721cb0ef41Sopenharmony_ci`undefined`.
3731cb0ef41Sopenharmony_ci
3741cb0ef41Sopenharmony_ci### Troubleshooting: Context loss
3751cb0ef41Sopenharmony_ci
3761cb0ef41Sopenharmony_ciIn most cases, `AsyncLocalStorage` works without issues. In rare situations, the
3771cb0ef41Sopenharmony_cicurrent store is lost in one of the asynchronous operations.
3781cb0ef41Sopenharmony_ci
3791cb0ef41Sopenharmony_ciIf your code is callback-based, it is enough to promisify it with
3801cb0ef41Sopenharmony_ci[`util.promisify()`][] so it starts working with native promises.
3811cb0ef41Sopenharmony_ci
3821cb0ef41Sopenharmony_ciIf you need to use a callback-based API or your code assumes
3831cb0ef41Sopenharmony_cia custom thenable implementation, use the [`AsyncResource`][] class
3841cb0ef41Sopenharmony_cito associate the asynchronous operation with the correct execution context.
3851cb0ef41Sopenharmony_ciFind the function call responsible for the context loss by logging the content
3861cb0ef41Sopenharmony_ciof `asyncLocalStorage.getStore()` after the calls you suspect are responsible
3871cb0ef41Sopenharmony_cifor the loss. When the code logs `undefined`, the last callback called is
3881cb0ef41Sopenharmony_ciprobably responsible for the context loss.
3891cb0ef41Sopenharmony_ci
3901cb0ef41Sopenharmony_ci## Class: `AsyncResource`
3911cb0ef41Sopenharmony_ci
3921cb0ef41Sopenharmony_ci<!-- YAML
3931cb0ef41Sopenharmony_cichanges:
3941cb0ef41Sopenharmony_ci - version: v16.4.0
3951cb0ef41Sopenharmony_ci   pr-url: https://github.com/nodejs/node/pull/37675
3961cb0ef41Sopenharmony_ci   description: AsyncResource is now Stable. Previously, it had been Experimental.
3971cb0ef41Sopenharmony_ci-->
3981cb0ef41Sopenharmony_ci
3991cb0ef41Sopenharmony_ciThe class `AsyncResource` is designed to be extended by the embedder's async
4001cb0ef41Sopenharmony_ciresources. Using this, users can easily trigger the lifetime events of their
4011cb0ef41Sopenharmony_ciown resources.
4021cb0ef41Sopenharmony_ci
4031cb0ef41Sopenharmony_ciThe `init` hook will trigger when an `AsyncResource` is instantiated.
4041cb0ef41Sopenharmony_ci
4051cb0ef41Sopenharmony_ciThe following is an overview of the `AsyncResource` API.
4061cb0ef41Sopenharmony_ci
4071cb0ef41Sopenharmony_ci```mjs
4081cb0ef41Sopenharmony_ciimport { AsyncResource, executionAsyncId } from 'node:async_hooks';
4091cb0ef41Sopenharmony_ci
4101cb0ef41Sopenharmony_ci// AsyncResource() is meant to be extended. Instantiating a
4111cb0ef41Sopenharmony_ci// new AsyncResource() also triggers init. If triggerAsyncId is omitted then
4121cb0ef41Sopenharmony_ci// async_hook.executionAsyncId() is used.
4131cb0ef41Sopenharmony_ciconst asyncResource = new AsyncResource(
4141cb0ef41Sopenharmony_ci  type, { triggerAsyncId: executionAsyncId(), requireManualDestroy: false },
4151cb0ef41Sopenharmony_ci);
4161cb0ef41Sopenharmony_ci
4171cb0ef41Sopenharmony_ci// Run a function in the execution context of the resource. This will
4181cb0ef41Sopenharmony_ci// * establish the context of the resource
4191cb0ef41Sopenharmony_ci// * trigger the AsyncHooks before callbacks
4201cb0ef41Sopenharmony_ci// * call the provided function `fn` with the supplied arguments
4211cb0ef41Sopenharmony_ci// * trigger the AsyncHooks after callbacks
4221cb0ef41Sopenharmony_ci// * restore the original execution context
4231cb0ef41Sopenharmony_ciasyncResource.runInAsyncScope(fn, thisArg, ...args);
4241cb0ef41Sopenharmony_ci
4251cb0ef41Sopenharmony_ci// Call AsyncHooks destroy callbacks.
4261cb0ef41Sopenharmony_ciasyncResource.emitDestroy();
4271cb0ef41Sopenharmony_ci
4281cb0ef41Sopenharmony_ci// Return the unique ID assigned to the AsyncResource instance.
4291cb0ef41Sopenharmony_ciasyncResource.asyncId();
4301cb0ef41Sopenharmony_ci
4311cb0ef41Sopenharmony_ci// Return the trigger ID for the AsyncResource instance.
4321cb0ef41Sopenharmony_ciasyncResource.triggerAsyncId();
4331cb0ef41Sopenharmony_ci```
4341cb0ef41Sopenharmony_ci
4351cb0ef41Sopenharmony_ci```cjs
4361cb0ef41Sopenharmony_ciconst { AsyncResource, executionAsyncId } = require('node:async_hooks');
4371cb0ef41Sopenharmony_ci
4381cb0ef41Sopenharmony_ci// AsyncResource() is meant to be extended. Instantiating a
4391cb0ef41Sopenharmony_ci// new AsyncResource() also triggers init. If triggerAsyncId is omitted then
4401cb0ef41Sopenharmony_ci// async_hook.executionAsyncId() is used.
4411cb0ef41Sopenharmony_ciconst asyncResource = new AsyncResource(
4421cb0ef41Sopenharmony_ci  type, { triggerAsyncId: executionAsyncId(), requireManualDestroy: false },
4431cb0ef41Sopenharmony_ci);
4441cb0ef41Sopenharmony_ci
4451cb0ef41Sopenharmony_ci// Run a function in the execution context of the resource. This will
4461cb0ef41Sopenharmony_ci// * establish the context of the resource
4471cb0ef41Sopenharmony_ci// * trigger the AsyncHooks before callbacks
4481cb0ef41Sopenharmony_ci// * call the provided function `fn` with the supplied arguments
4491cb0ef41Sopenharmony_ci// * trigger the AsyncHooks after callbacks
4501cb0ef41Sopenharmony_ci// * restore the original execution context
4511cb0ef41Sopenharmony_ciasyncResource.runInAsyncScope(fn, thisArg, ...args);
4521cb0ef41Sopenharmony_ci
4531cb0ef41Sopenharmony_ci// Call AsyncHooks destroy callbacks.
4541cb0ef41Sopenharmony_ciasyncResource.emitDestroy();
4551cb0ef41Sopenharmony_ci
4561cb0ef41Sopenharmony_ci// Return the unique ID assigned to the AsyncResource instance.
4571cb0ef41Sopenharmony_ciasyncResource.asyncId();
4581cb0ef41Sopenharmony_ci
4591cb0ef41Sopenharmony_ci// Return the trigger ID for the AsyncResource instance.
4601cb0ef41Sopenharmony_ciasyncResource.triggerAsyncId();
4611cb0ef41Sopenharmony_ci```
4621cb0ef41Sopenharmony_ci
4631cb0ef41Sopenharmony_ci### `new AsyncResource(type[, options])`
4641cb0ef41Sopenharmony_ci
4651cb0ef41Sopenharmony_ci* `type` {string} The type of async event.
4661cb0ef41Sopenharmony_ci* `options` {Object}
4671cb0ef41Sopenharmony_ci  * `triggerAsyncId` {number} The ID of the execution context that created this
4681cb0ef41Sopenharmony_ci    async event. **Default:** `executionAsyncId()`.
4691cb0ef41Sopenharmony_ci  * `requireManualDestroy` {boolean} If set to `true`, disables `emitDestroy`
4701cb0ef41Sopenharmony_ci    when the object is garbage collected. This usually does not need to be set
4711cb0ef41Sopenharmony_ci    (even if `emitDestroy` is called manually), unless the resource's `asyncId`
4721cb0ef41Sopenharmony_ci    is retrieved and the sensitive API's `emitDestroy` is called with it.
4731cb0ef41Sopenharmony_ci    When set to `false`, the `emitDestroy` call on garbage collection
4741cb0ef41Sopenharmony_ci    will only take place if there is at least one active `destroy` hook.
4751cb0ef41Sopenharmony_ci    **Default:** `false`.
4761cb0ef41Sopenharmony_ci
4771cb0ef41Sopenharmony_ciExample usage:
4781cb0ef41Sopenharmony_ci
4791cb0ef41Sopenharmony_ci```js
4801cb0ef41Sopenharmony_ciclass DBQuery extends AsyncResource {
4811cb0ef41Sopenharmony_ci  constructor(db) {
4821cb0ef41Sopenharmony_ci    super('DBQuery');
4831cb0ef41Sopenharmony_ci    this.db = db;
4841cb0ef41Sopenharmony_ci  }
4851cb0ef41Sopenharmony_ci
4861cb0ef41Sopenharmony_ci  getInfo(query, callback) {
4871cb0ef41Sopenharmony_ci    this.db.get(query, (err, data) => {
4881cb0ef41Sopenharmony_ci      this.runInAsyncScope(callback, null, err, data);
4891cb0ef41Sopenharmony_ci    });
4901cb0ef41Sopenharmony_ci  }
4911cb0ef41Sopenharmony_ci
4921cb0ef41Sopenharmony_ci  close() {
4931cb0ef41Sopenharmony_ci    this.db = null;
4941cb0ef41Sopenharmony_ci    this.emitDestroy();
4951cb0ef41Sopenharmony_ci  }
4961cb0ef41Sopenharmony_ci}
4971cb0ef41Sopenharmony_ci```
4981cb0ef41Sopenharmony_ci
4991cb0ef41Sopenharmony_ci### Static method: `AsyncResource.bind(fn[, type[, thisArg]])`
5001cb0ef41Sopenharmony_ci
5011cb0ef41Sopenharmony_ci<!-- YAML
5021cb0ef41Sopenharmony_ciadded:
5031cb0ef41Sopenharmony_ci  - v14.8.0
5041cb0ef41Sopenharmony_ci  - v12.19.0
5051cb0ef41Sopenharmony_cichanges:
5061cb0ef41Sopenharmony_ci  - version: v17.8.0
5071cb0ef41Sopenharmony_ci    pr-url: https://github.com/nodejs/node/pull/42177
5081cb0ef41Sopenharmony_ci    description: Changed the default when `thisArg` is undefined to use `this`
5091cb0ef41Sopenharmony_ci                 from the caller.
5101cb0ef41Sopenharmony_ci  - version: v16.0.0
5111cb0ef41Sopenharmony_ci    pr-url: https://github.com/nodejs/node/pull/36782
5121cb0ef41Sopenharmony_ci    description: Added optional thisArg.
5131cb0ef41Sopenharmony_ci-->
5141cb0ef41Sopenharmony_ci
5151cb0ef41Sopenharmony_ci* `fn` {Function} The function to bind to the current execution context.
5161cb0ef41Sopenharmony_ci* `type` {string} An optional name to associate with the underlying
5171cb0ef41Sopenharmony_ci  `AsyncResource`.
5181cb0ef41Sopenharmony_ci* `thisArg` {any}
5191cb0ef41Sopenharmony_ci
5201cb0ef41Sopenharmony_ciBinds the given function to the current execution context.
5211cb0ef41Sopenharmony_ci
5221cb0ef41Sopenharmony_ciThe returned function will have an `asyncResource` property referencing
5231cb0ef41Sopenharmony_cithe `AsyncResource` to which the function is bound.
5241cb0ef41Sopenharmony_ci
5251cb0ef41Sopenharmony_ci### `asyncResource.bind(fn[, thisArg])`
5261cb0ef41Sopenharmony_ci
5271cb0ef41Sopenharmony_ci<!-- YAML
5281cb0ef41Sopenharmony_ciadded:
5291cb0ef41Sopenharmony_ci  - v14.8.0
5301cb0ef41Sopenharmony_ci  - v12.19.0
5311cb0ef41Sopenharmony_cichanges:
5321cb0ef41Sopenharmony_ci  - version: v17.8.0
5331cb0ef41Sopenharmony_ci    pr-url: https://github.com/nodejs/node/pull/42177
5341cb0ef41Sopenharmony_ci    description: Changed the default when `thisArg` is undefined to use `this`
5351cb0ef41Sopenharmony_ci                 from the caller.
5361cb0ef41Sopenharmony_ci  - version: v16.0.0
5371cb0ef41Sopenharmony_ci    pr-url: https://github.com/nodejs/node/pull/36782
5381cb0ef41Sopenharmony_ci    description: Added optional thisArg.
5391cb0ef41Sopenharmony_ci-->
5401cb0ef41Sopenharmony_ci
5411cb0ef41Sopenharmony_ci* `fn` {Function} The function to bind to the current `AsyncResource`.
5421cb0ef41Sopenharmony_ci* `thisArg` {any}
5431cb0ef41Sopenharmony_ci
5441cb0ef41Sopenharmony_ciBinds the given function to execute to this `AsyncResource`'s scope.
5451cb0ef41Sopenharmony_ci
5461cb0ef41Sopenharmony_ciThe returned function will have an `asyncResource` property referencing
5471cb0ef41Sopenharmony_cithe `AsyncResource` to which the function is bound.
5481cb0ef41Sopenharmony_ci
5491cb0ef41Sopenharmony_ci### `asyncResource.runInAsyncScope(fn[, thisArg, ...args])`
5501cb0ef41Sopenharmony_ci
5511cb0ef41Sopenharmony_ci<!-- YAML
5521cb0ef41Sopenharmony_ciadded: v9.6.0
5531cb0ef41Sopenharmony_ci-->
5541cb0ef41Sopenharmony_ci
5551cb0ef41Sopenharmony_ci* `fn` {Function} The function to call in the execution context of this async
5561cb0ef41Sopenharmony_ci  resource.
5571cb0ef41Sopenharmony_ci* `thisArg` {any} The receiver to be used for the function call.
5581cb0ef41Sopenharmony_ci* `...args` {any} Optional arguments to pass to the function.
5591cb0ef41Sopenharmony_ci
5601cb0ef41Sopenharmony_ciCall the provided function with the provided arguments in the execution context
5611cb0ef41Sopenharmony_ciof the async resource. This will establish the context, trigger the AsyncHooks
5621cb0ef41Sopenharmony_cibefore callbacks, call the function, trigger the AsyncHooks after callbacks, and
5631cb0ef41Sopenharmony_cithen restore the original execution context.
5641cb0ef41Sopenharmony_ci
5651cb0ef41Sopenharmony_ci### `asyncResource.emitDestroy()`
5661cb0ef41Sopenharmony_ci
5671cb0ef41Sopenharmony_ci* Returns: {AsyncResource} A reference to `asyncResource`.
5681cb0ef41Sopenharmony_ci
5691cb0ef41Sopenharmony_ciCall all `destroy` hooks. This should only ever be called once. An error will
5701cb0ef41Sopenharmony_cibe thrown if it is called more than once. This **must** be manually called. If
5711cb0ef41Sopenharmony_cithe resource is left to be collected by the GC then the `destroy` hooks will
5721cb0ef41Sopenharmony_cinever be called.
5731cb0ef41Sopenharmony_ci
5741cb0ef41Sopenharmony_ci### `asyncResource.asyncId()`
5751cb0ef41Sopenharmony_ci
5761cb0ef41Sopenharmony_ci* Returns: {number} The unique `asyncId` assigned to the resource.
5771cb0ef41Sopenharmony_ci
5781cb0ef41Sopenharmony_ci### `asyncResource.triggerAsyncId()`
5791cb0ef41Sopenharmony_ci
5801cb0ef41Sopenharmony_ci* Returns: {number} The same `triggerAsyncId` that is passed to the
5811cb0ef41Sopenharmony_ci  `AsyncResource` constructor.
5821cb0ef41Sopenharmony_ci
5831cb0ef41Sopenharmony_ci<a id="async-resource-worker-pool"></a>
5841cb0ef41Sopenharmony_ci
5851cb0ef41Sopenharmony_ci### Using `AsyncResource` for a `Worker` thread pool
5861cb0ef41Sopenharmony_ci
5871cb0ef41Sopenharmony_ciThe following example shows how to use the `AsyncResource` class to properly
5881cb0ef41Sopenharmony_ciprovide async tracking for a [`Worker`][] pool. Other resource pools, such as
5891cb0ef41Sopenharmony_cidatabase connection pools, can follow a similar model.
5901cb0ef41Sopenharmony_ci
5911cb0ef41Sopenharmony_ciAssuming that the task is adding two numbers, using a file named
5921cb0ef41Sopenharmony_ci`task_processor.js` with the following content:
5931cb0ef41Sopenharmony_ci
5941cb0ef41Sopenharmony_ci```mjs
5951cb0ef41Sopenharmony_ciimport { parentPort } from 'node:worker_threads';
5961cb0ef41Sopenharmony_ciparentPort.on('message', (task) => {
5971cb0ef41Sopenharmony_ci  parentPort.postMessage(task.a + task.b);
5981cb0ef41Sopenharmony_ci});
5991cb0ef41Sopenharmony_ci```
6001cb0ef41Sopenharmony_ci
6011cb0ef41Sopenharmony_ci```cjs
6021cb0ef41Sopenharmony_ciconst { parentPort } = require('node:worker_threads');
6031cb0ef41Sopenharmony_ciparentPort.on('message', (task) => {
6041cb0ef41Sopenharmony_ci  parentPort.postMessage(task.a + task.b);
6051cb0ef41Sopenharmony_ci});
6061cb0ef41Sopenharmony_ci```
6071cb0ef41Sopenharmony_ci
6081cb0ef41Sopenharmony_cia Worker pool around it could use the following structure:
6091cb0ef41Sopenharmony_ci
6101cb0ef41Sopenharmony_ci```mjs
6111cb0ef41Sopenharmony_ciimport { AsyncResource } from 'node:async_hooks';
6121cb0ef41Sopenharmony_ciimport { EventEmitter } from 'node:events';
6131cb0ef41Sopenharmony_ciimport path from 'node:path';
6141cb0ef41Sopenharmony_ciimport { Worker } from 'node:worker_threads';
6151cb0ef41Sopenharmony_ci
6161cb0ef41Sopenharmony_ciconst kTaskInfo = Symbol('kTaskInfo');
6171cb0ef41Sopenharmony_ciconst kWorkerFreedEvent = Symbol('kWorkerFreedEvent');
6181cb0ef41Sopenharmony_ci
6191cb0ef41Sopenharmony_ciclass WorkerPoolTaskInfo extends AsyncResource {
6201cb0ef41Sopenharmony_ci  constructor(callback) {
6211cb0ef41Sopenharmony_ci    super('WorkerPoolTaskInfo');
6221cb0ef41Sopenharmony_ci    this.callback = callback;
6231cb0ef41Sopenharmony_ci  }
6241cb0ef41Sopenharmony_ci
6251cb0ef41Sopenharmony_ci  done(err, result) {
6261cb0ef41Sopenharmony_ci    this.runInAsyncScope(this.callback, null, err, result);
6271cb0ef41Sopenharmony_ci    this.emitDestroy();  // `TaskInfo`s are used only once.
6281cb0ef41Sopenharmony_ci  }
6291cb0ef41Sopenharmony_ci}
6301cb0ef41Sopenharmony_ci
6311cb0ef41Sopenharmony_ciexport default class WorkerPool extends EventEmitter {
6321cb0ef41Sopenharmony_ci  constructor(numThreads) {
6331cb0ef41Sopenharmony_ci    super();
6341cb0ef41Sopenharmony_ci    this.numThreads = numThreads;
6351cb0ef41Sopenharmony_ci    this.workers = [];
6361cb0ef41Sopenharmony_ci    this.freeWorkers = [];
6371cb0ef41Sopenharmony_ci    this.tasks = [];
6381cb0ef41Sopenharmony_ci
6391cb0ef41Sopenharmony_ci    for (let i = 0; i < numThreads; i++)
6401cb0ef41Sopenharmony_ci      this.addNewWorker();
6411cb0ef41Sopenharmony_ci
6421cb0ef41Sopenharmony_ci    // Any time the kWorkerFreedEvent is emitted, dispatch
6431cb0ef41Sopenharmony_ci    // the next task pending in the queue, if any.
6441cb0ef41Sopenharmony_ci    this.on(kWorkerFreedEvent, () => {
6451cb0ef41Sopenharmony_ci      if (this.tasks.length > 0) {
6461cb0ef41Sopenharmony_ci        const { task, callback } = this.tasks.shift();
6471cb0ef41Sopenharmony_ci        this.runTask(task, callback);
6481cb0ef41Sopenharmony_ci      }
6491cb0ef41Sopenharmony_ci    });
6501cb0ef41Sopenharmony_ci  }
6511cb0ef41Sopenharmony_ci
6521cb0ef41Sopenharmony_ci  addNewWorker() {
6531cb0ef41Sopenharmony_ci    const worker = new Worker(new URL('task_processor.js', import.meta.url));
6541cb0ef41Sopenharmony_ci    worker.on('message', (result) => {
6551cb0ef41Sopenharmony_ci      // In case of success: Call the callback that was passed to `runTask`,
6561cb0ef41Sopenharmony_ci      // remove the `TaskInfo` associated with the Worker, and mark it as free
6571cb0ef41Sopenharmony_ci      // again.
6581cb0ef41Sopenharmony_ci      worker[kTaskInfo].done(null, result);
6591cb0ef41Sopenharmony_ci      worker[kTaskInfo] = null;
6601cb0ef41Sopenharmony_ci      this.freeWorkers.push(worker);
6611cb0ef41Sopenharmony_ci      this.emit(kWorkerFreedEvent);
6621cb0ef41Sopenharmony_ci    });
6631cb0ef41Sopenharmony_ci    worker.on('error', (err) => {
6641cb0ef41Sopenharmony_ci      // In case of an uncaught exception: Call the callback that was passed to
6651cb0ef41Sopenharmony_ci      // `runTask` with the error.
6661cb0ef41Sopenharmony_ci      if (worker[kTaskInfo])
6671cb0ef41Sopenharmony_ci        worker[kTaskInfo].done(err, null);
6681cb0ef41Sopenharmony_ci      else
6691cb0ef41Sopenharmony_ci        this.emit('error', err);
6701cb0ef41Sopenharmony_ci      // Remove the worker from the list and start a new Worker to replace the
6711cb0ef41Sopenharmony_ci      // current one.
6721cb0ef41Sopenharmony_ci      this.workers.splice(this.workers.indexOf(worker), 1);
6731cb0ef41Sopenharmony_ci      this.addNewWorker();
6741cb0ef41Sopenharmony_ci    });
6751cb0ef41Sopenharmony_ci    this.workers.push(worker);
6761cb0ef41Sopenharmony_ci    this.freeWorkers.push(worker);
6771cb0ef41Sopenharmony_ci    this.emit(kWorkerFreedEvent);
6781cb0ef41Sopenharmony_ci  }
6791cb0ef41Sopenharmony_ci
6801cb0ef41Sopenharmony_ci  runTask(task, callback) {
6811cb0ef41Sopenharmony_ci    if (this.freeWorkers.length === 0) {
6821cb0ef41Sopenharmony_ci      // No free threads, wait until a worker thread becomes free.
6831cb0ef41Sopenharmony_ci      this.tasks.push({ task, callback });
6841cb0ef41Sopenharmony_ci      return;
6851cb0ef41Sopenharmony_ci    }
6861cb0ef41Sopenharmony_ci
6871cb0ef41Sopenharmony_ci    const worker = this.freeWorkers.pop();
6881cb0ef41Sopenharmony_ci    worker[kTaskInfo] = new WorkerPoolTaskInfo(callback);
6891cb0ef41Sopenharmony_ci    worker.postMessage(task);
6901cb0ef41Sopenharmony_ci  }
6911cb0ef41Sopenharmony_ci
6921cb0ef41Sopenharmony_ci  close() {
6931cb0ef41Sopenharmony_ci    for (const worker of this.workers) worker.terminate();
6941cb0ef41Sopenharmony_ci  }
6951cb0ef41Sopenharmony_ci}
6961cb0ef41Sopenharmony_ci```
6971cb0ef41Sopenharmony_ci
6981cb0ef41Sopenharmony_ci```cjs
6991cb0ef41Sopenharmony_ciconst { AsyncResource } = require('node:async_hooks');
7001cb0ef41Sopenharmony_ciconst { EventEmitter } = require('node:events');
7011cb0ef41Sopenharmony_ciconst path = require('node:path');
7021cb0ef41Sopenharmony_ciconst { Worker } = require('node:worker_threads');
7031cb0ef41Sopenharmony_ci
7041cb0ef41Sopenharmony_ciconst kTaskInfo = Symbol('kTaskInfo');
7051cb0ef41Sopenharmony_ciconst kWorkerFreedEvent = Symbol('kWorkerFreedEvent');
7061cb0ef41Sopenharmony_ci
7071cb0ef41Sopenharmony_ciclass WorkerPoolTaskInfo extends AsyncResource {
7081cb0ef41Sopenharmony_ci  constructor(callback) {
7091cb0ef41Sopenharmony_ci    super('WorkerPoolTaskInfo');
7101cb0ef41Sopenharmony_ci    this.callback = callback;
7111cb0ef41Sopenharmony_ci  }
7121cb0ef41Sopenharmony_ci
7131cb0ef41Sopenharmony_ci  done(err, result) {
7141cb0ef41Sopenharmony_ci    this.runInAsyncScope(this.callback, null, err, result);
7151cb0ef41Sopenharmony_ci    this.emitDestroy();  // `TaskInfo`s are used only once.
7161cb0ef41Sopenharmony_ci  }
7171cb0ef41Sopenharmony_ci}
7181cb0ef41Sopenharmony_ci
7191cb0ef41Sopenharmony_ciclass WorkerPool extends EventEmitter {
7201cb0ef41Sopenharmony_ci  constructor(numThreads) {
7211cb0ef41Sopenharmony_ci    super();
7221cb0ef41Sopenharmony_ci    this.numThreads = numThreads;
7231cb0ef41Sopenharmony_ci    this.workers = [];
7241cb0ef41Sopenharmony_ci    this.freeWorkers = [];
7251cb0ef41Sopenharmony_ci    this.tasks = [];
7261cb0ef41Sopenharmony_ci
7271cb0ef41Sopenharmony_ci    for (let i = 0; i < numThreads; i++)
7281cb0ef41Sopenharmony_ci      this.addNewWorker();
7291cb0ef41Sopenharmony_ci
7301cb0ef41Sopenharmony_ci    // Any time the kWorkerFreedEvent is emitted, dispatch
7311cb0ef41Sopenharmony_ci    // the next task pending in the queue, if any.
7321cb0ef41Sopenharmony_ci    this.on(kWorkerFreedEvent, () => {
7331cb0ef41Sopenharmony_ci      if (this.tasks.length > 0) {
7341cb0ef41Sopenharmony_ci        const { task, callback } = this.tasks.shift();
7351cb0ef41Sopenharmony_ci        this.runTask(task, callback);
7361cb0ef41Sopenharmony_ci      }
7371cb0ef41Sopenharmony_ci    });
7381cb0ef41Sopenharmony_ci  }
7391cb0ef41Sopenharmony_ci
7401cb0ef41Sopenharmony_ci  addNewWorker() {
7411cb0ef41Sopenharmony_ci    const worker = new Worker(path.resolve(__dirname, 'task_processor.js'));
7421cb0ef41Sopenharmony_ci    worker.on('message', (result) => {
7431cb0ef41Sopenharmony_ci      // In case of success: Call the callback that was passed to `runTask`,
7441cb0ef41Sopenharmony_ci      // remove the `TaskInfo` associated with the Worker, and mark it as free
7451cb0ef41Sopenharmony_ci      // again.
7461cb0ef41Sopenharmony_ci      worker[kTaskInfo].done(null, result);
7471cb0ef41Sopenharmony_ci      worker[kTaskInfo] = null;
7481cb0ef41Sopenharmony_ci      this.freeWorkers.push(worker);
7491cb0ef41Sopenharmony_ci      this.emit(kWorkerFreedEvent);
7501cb0ef41Sopenharmony_ci    });
7511cb0ef41Sopenharmony_ci    worker.on('error', (err) => {
7521cb0ef41Sopenharmony_ci      // In case of an uncaught exception: Call the callback that was passed to
7531cb0ef41Sopenharmony_ci      // `runTask` with the error.
7541cb0ef41Sopenharmony_ci      if (worker[kTaskInfo])
7551cb0ef41Sopenharmony_ci        worker[kTaskInfo].done(err, null);
7561cb0ef41Sopenharmony_ci      else
7571cb0ef41Sopenharmony_ci        this.emit('error', err);
7581cb0ef41Sopenharmony_ci      // Remove the worker from the list and start a new Worker to replace the
7591cb0ef41Sopenharmony_ci      // current one.
7601cb0ef41Sopenharmony_ci      this.workers.splice(this.workers.indexOf(worker), 1);
7611cb0ef41Sopenharmony_ci      this.addNewWorker();
7621cb0ef41Sopenharmony_ci    });
7631cb0ef41Sopenharmony_ci    this.workers.push(worker);
7641cb0ef41Sopenharmony_ci    this.freeWorkers.push(worker);
7651cb0ef41Sopenharmony_ci    this.emit(kWorkerFreedEvent);
7661cb0ef41Sopenharmony_ci  }
7671cb0ef41Sopenharmony_ci
7681cb0ef41Sopenharmony_ci  runTask(task, callback) {
7691cb0ef41Sopenharmony_ci    if (this.freeWorkers.length === 0) {
7701cb0ef41Sopenharmony_ci      // No free threads, wait until a worker thread becomes free.
7711cb0ef41Sopenharmony_ci      this.tasks.push({ task, callback });
7721cb0ef41Sopenharmony_ci      return;
7731cb0ef41Sopenharmony_ci    }
7741cb0ef41Sopenharmony_ci
7751cb0ef41Sopenharmony_ci    const worker = this.freeWorkers.pop();
7761cb0ef41Sopenharmony_ci    worker[kTaskInfo] = new WorkerPoolTaskInfo(callback);
7771cb0ef41Sopenharmony_ci    worker.postMessage(task);
7781cb0ef41Sopenharmony_ci  }
7791cb0ef41Sopenharmony_ci
7801cb0ef41Sopenharmony_ci  close() {
7811cb0ef41Sopenharmony_ci    for (const worker of this.workers) worker.terminate();
7821cb0ef41Sopenharmony_ci  }
7831cb0ef41Sopenharmony_ci}
7841cb0ef41Sopenharmony_ci
7851cb0ef41Sopenharmony_cimodule.exports = WorkerPool;
7861cb0ef41Sopenharmony_ci```
7871cb0ef41Sopenharmony_ci
7881cb0ef41Sopenharmony_ciWithout the explicit tracking added by the `WorkerPoolTaskInfo` objects,
7891cb0ef41Sopenharmony_ciit would appear that the callbacks are associated with the individual `Worker`
7901cb0ef41Sopenharmony_ciobjects. However, the creation of the `Worker`s is not associated with the
7911cb0ef41Sopenharmony_cicreation of the tasks and does not provide information about when tasks
7921cb0ef41Sopenharmony_ciwere scheduled.
7931cb0ef41Sopenharmony_ci
7941cb0ef41Sopenharmony_ciThis pool could be used as follows:
7951cb0ef41Sopenharmony_ci
7961cb0ef41Sopenharmony_ci```mjs
7971cb0ef41Sopenharmony_ciimport WorkerPool from './worker_pool.js';
7981cb0ef41Sopenharmony_ciimport os from 'node:os';
7991cb0ef41Sopenharmony_ci
8001cb0ef41Sopenharmony_ciconst pool = new WorkerPool(os.availableParallelism());
8011cb0ef41Sopenharmony_ci
8021cb0ef41Sopenharmony_cilet finished = 0;
8031cb0ef41Sopenharmony_cifor (let i = 0; i < 10; i++) {
8041cb0ef41Sopenharmony_ci  pool.runTask({ a: 42, b: 100 }, (err, result) => {
8051cb0ef41Sopenharmony_ci    console.log(i, err, result);
8061cb0ef41Sopenharmony_ci    if (++finished === 10)
8071cb0ef41Sopenharmony_ci      pool.close();
8081cb0ef41Sopenharmony_ci  });
8091cb0ef41Sopenharmony_ci}
8101cb0ef41Sopenharmony_ci```
8111cb0ef41Sopenharmony_ci
8121cb0ef41Sopenharmony_ci```cjs
8131cb0ef41Sopenharmony_ciconst WorkerPool = require('./worker_pool.js');
8141cb0ef41Sopenharmony_ciconst os = require('node:os');
8151cb0ef41Sopenharmony_ci
8161cb0ef41Sopenharmony_ciconst pool = new WorkerPool(os.availableParallelism());
8171cb0ef41Sopenharmony_ci
8181cb0ef41Sopenharmony_cilet finished = 0;
8191cb0ef41Sopenharmony_cifor (let i = 0; i < 10; i++) {
8201cb0ef41Sopenharmony_ci  pool.runTask({ a: 42, b: 100 }, (err, result) => {
8211cb0ef41Sopenharmony_ci    console.log(i, err, result);
8221cb0ef41Sopenharmony_ci    if (++finished === 10)
8231cb0ef41Sopenharmony_ci      pool.close();
8241cb0ef41Sopenharmony_ci  });
8251cb0ef41Sopenharmony_ci}
8261cb0ef41Sopenharmony_ci```
8271cb0ef41Sopenharmony_ci
8281cb0ef41Sopenharmony_ci### Integrating `AsyncResource` with `EventEmitter`
8291cb0ef41Sopenharmony_ci
8301cb0ef41Sopenharmony_ciEvent listeners triggered by an [`EventEmitter`][] may be run in a different
8311cb0ef41Sopenharmony_ciexecution context than the one that was active when `eventEmitter.on()` was
8321cb0ef41Sopenharmony_cicalled.
8331cb0ef41Sopenharmony_ci
8341cb0ef41Sopenharmony_ciThe following example shows how to use the `AsyncResource` class to properly
8351cb0ef41Sopenharmony_ciassociate an event listener with the correct execution context. The same
8361cb0ef41Sopenharmony_ciapproach can be applied to a [`Stream`][] or a similar event-driven class.
8371cb0ef41Sopenharmony_ci
8381cb0ef41Sopenharmony_ci```mjs
8391cb0ef41Sopenharmony_ciimport { createServer } from 'node:http';
8401cb0ef41Sopenharmony_ciimport { AsyncResource, executionAsyncId } from 'node:async_hooks';
8411cb0ef41Sopenharmony_ci
8421cb0ef41Sopenharmony_ciconst server = createServer((req, res) => {
8431cb0ef41Sopenharmony_ci  req.on('close', AsyncResource.bind(() => {
8441cb0ef41Sopenharmony_ci    // Execution context is bound to the current outer scope.
8451cb0ef41Sopenharmony_ci  }));
8461cb0ef41Sopenharmony_ci  req.on('close', () => {
8471cb0ef41Sopenharmony_ci    // Execution context is bound to the scope that caused 'close' to emit.
8481cb0ef41Sopenharmony_ci  });
8491cb0ef41Sopenharmony_ci  res.end();
8501cb0ef41Sopenharmony_ci}).listen(3000);
8511cb0ef41Sopenharmony_ci```
8521cb0ef41Sopenharmony_ci
8531cb0ef41Sopenharmony_ci```cjs
8541cb0ef41Sopenharmony_ciconst { createServer } = require('node:http');
8551cb0ef41Sopenharmony_ciconst { AsyncResource, executionAsyncId } = require('node:async_hooks');
8561cb0ef41Sopenharmony_ci
8571cb0ef41Sopenharmony_ciconst server = createServer((req, res) => {
8581cb0ef41Sopenharmony_ci  req.on('close', AsyncResource.bind(() => {
8591cb0ef41Sopenharmony_ci    // Execution context is bound to the current outer scope.
8601cb0ef41Sopenharmony_ci  }));
8611cb0ef41Sopenharmony_ci  req.on('close', () => {
8621cb0ef41Sopenharmony_ci    // Execution context is bound to the scope that caused 'close' to emit.
8631cb0ef41Sopenharmony_ci  });
8641cb0ef41Sopenharmony_ci  res.end();
8651cb0ef41Sopenharmony_ci}).listen(3000);
8661cb0ef41Sopenharmony_ci```
8671cb0ef41Sopenharmony_ci
8681cb0ef41Sopenharmony_ci[`AsyncResource`]: #class-asyncresource
8691cb0ef41Sopenharmony_ci[`EventEmitter`]: events.md#class-eventemitter
8701cb0ef41Sopenharmony_ci[`Stream`]: stream.md#stream
8711cb0ef41Sopenharmony_ci[`Worker`]: worker_threads.md#class-worker
8721cb0ef41Sopenharmony_ci[`util.promisify()`]: util.md#utilpromisifyoriginal
873