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