1# Asynchronous context tracking 2 3<!--introduced_in=v16.4.0--> 4 5> Stability: 2 - Stable 6 7<!-- source_link=lib/async_hooks.js --> 8 9## Introduction 10 11These classes are used to associate state and propagate it throughout 12callbacks and promise chains. 13They allow storing data throughout the lifetime of a web request 14or any other asynchronous duration. It is similar to thread-local storage 15in other languages. 16 17The `AsyncLocalStorage` and `AsyncResource` classes are part of the 18`node:async_hooks` module: 19 20```mjs 21import { AsyncLocalStorage, AsyncResource } from 'node:async_hooks'; 22``` 23 24```cjs 25const { AsyncLocalStorage, AsyncResource } = require('node:async_hooks'); 26``` 27 28## Class: `AsyncLocalStorage` 29 30<!-- YAML 31added: 32 - v13.10.0 33 - v12.17.0 34changes: 35 - version: v16.4.0 36 pr-url: https://github.com/nodejs/node/pull/37675 37 description: AsyncLocalStorage is now Stable. Previously, it had been Experimental. 38--> 39 40This class creates stores that stay coherent through asynchronous operations. 41 42While you can create your own implementation on top of the `node:async_hooks` 43module, `AsyncLocalStorage` should be preferred as it is a performant and memory 44safe implementation that involves significant optimizations that are non-obvious 45to implement. 46 47The following example uses `AsyncLocalStorage` to build a simple logger 48that assigns IDs to incoming HTTP requests and includes them in messages 49logged within each request. 50 51```mjs 52import http from 'node:http'; 53import { AsyncLocalStorage } from 'node:async_hooks'; 54 55const asyncLocalStorage = new AsyncLocalStorage(); 56 57function logWithId(msg) { 58 const id = asyncLocalStorage.getStore(); 59 console.log(`${id !== undefined ? id : '-'}:`, msg); 60} 61 62let idSeq = 0; 63http.createServer((req, res) => { 64 asyncLocalStorage.run(idSeq++, () => { 65 logWithId('start'); 66 // Imagine any chain of async operations here 67 setImmediate(() => { 68 logWithId('finish'); 69 res.end(); 70 }); 71 }); 72}).listen(8080); 73 74http.get('http://localhost:8080'); 75http.get('http://localhost:8080'); 76// Prints: 77// 0: start 78// 1: start 79// 0: finish 80// 1: finish 81``` 82 83```cjs 84const http = require('node:http'); 85const { AsyncLocalStorage } = require('node:async_hooks'); 86 87const asyncLocalStorage = new AsyncLocalStorage(); 88 89function logWithId(msg) { 90 const id = asyncLocalStorage.getStore(); 91 console.log(`${id !== undefined ? id : '-'}:`, msg); 92} 93 94let idSeq = 0; 95http.createServer((req, res) => { 96 asyncLocalStorage.run(idSeq++, () => { 97 logWithId('start'); 98 // Imagine any chain of async operations here 99 setImmediate(() => { 100 logWithId('finish'); 101 res.end(); 102 }); 103 }); 104}).listen(8080); 105 106http.get('http://localhost:8080'); 107http.get('http://localhost:8080'); 108// Prints: 109// 0: start 110// 1: start 111// 0: finish 112// 1: finish 113``` 114 115Each instance of `AsyncLocalStorage` maintains an independent storage context. 116Multiple instances can safely exist simultaneously without risk of interfering 117with each other's data. 118 119### `new AsyncLocalStorage()` 120 121<!-- YAML 122added: 123 - v13.10.0 124 - v12.17.0 125changes: 126 - version: v18.16.0 127 pr-url: https://github.com/nodejs/node/pull/46386 128 description: Removed experimental onPropagate option. 129 - version: v18.13.0 130 pr-url: https://github.com/nodejs/node/pull/45386 131 description: Add option onPropagate. 132--> 133 134Creates a new instance of `AsyncLocalStorage`. Store is only provided within a 135`run()` call or after an `enterWith()` call. 136 137### Static method: `AsyncLocalStorage.bind(fn)` 138 139<!-- YAML 140added: v18.16.0 141--> 142 143> Stability: 1 - Experimental 144 145* `fn` {Function} The function to bind to the current execution context. 146* Returns: {Function} A new function that calls `fn` within the captured 147 execution context. 148 149Binds the given function to the current execution context. 150 151### Static method: `AsyncLocalStorage.snapshot()` 152 153<!-- YAML 154added: v18.16.0 155--> 156 157> Stability: 1 - Experimental 158 159* Returns: {Function} A new function with the signature 160 `(fn: (...args) : R, ...args) : R`. 161 162Captures the current execution context and returns a function that accepts a 163function as an argument. Whenever the returned function is called, it 164calls the function passed to it within the captured context. 165 166```js 167const asyncLocalStorage = new AsyncLocalStorage(); 168const runInAsyncScope = asyncLocalStorage.run(123, () => AsyncLocalStorage.snapshot()); 169const result = asyncLocalStorage.run(321, () => runInAsyncScope(() => asyncLocalStorage.getStore())); 170console.log(result); // returns 123 171``` 172 173AsyncLocalStorage.snapshot() can replace the use of AsyncResource for simple 174async context tracking purposes, for example: 175 176```js 177class Foo { 178 #runInAsyncScope = AsyncLocalStorage.snapshot(); 179 180 get() { return this.#runInAsyncScope(() => asyncLocalStorage.getStore()); } 181} 182 183const foo = asyncLocalStorage.run(123, () => new Foo()); 184console.log(asyncLocalStorage.run(321, () => foo.get())); // returns 123 185``` 186 187### `asyncLocalStorage.disable()` 188 189<!-- YAML 190added: 191 - v13.10.0 192 - v12.17.0 193--> 194 195> Stability: 1 - Experimental 196 197Disables the instance of `AsyncLocalStorage`. All subsequent calls 198to `asyncLocalStorage.getStore()` will return `undefined` until 199`asyncLocalStorage.run()` or `asyncLocalStorage.enterWith()` is called again. 200 201When calling `asyncLocalStorage.disable()`, all current contexts linked to the 202instance will be exited. 203 204Calling `asyncLocalStorage.disable()` is required before the 205`asyncLocalStorage` can be garbage collected. This does not apply to stores 206provided by the `asyncLocalStorage`, as those objects are garbage collected 207along with the corresponding async resources. 208 209Use this method when the `asyncLocalStorage` is not in use anymore 210in the current process. 211 212### `asyncLocalStorage.getStore()` 213 214<!-- YAML 215added: 216 - v13.10.0 217 - v12.17.0 218--> 219 220* Returns: {any} 221 222Returns the current store. 223If called outside of an asynchronous context initialized by 224calling `asyncLocalStorage.run()` or `asyncLocalStorage.enterWith()`, it 225returns `undefined`. 226 227### `asyncLocalStorage.enterWith(store)` 228 229<!-- YAML 230added: 231 - v13.11.0 232 - v12.17.0 233--> 234 235> Stability: 1 - Experimental 236 237* `store` {any} 238 239Transitions into the context for the remainder of the current 240synchronous execution and then persists the store through any following 241asynchronous calls. 242 243Example: 244 245```js 246const store = { id: 1 }; 247// Replaces previous store with the given store object 248asyncLocalStorage.enterWith(store); 249asyncLocalStorage.getStore(); // Returns the store object 250someAsyncOperation(() => { 251 asyncLocalStorage.getStore(); // Returns the same object 252}); 253``` 254 255This transition will continue for the _entire_ synchronous execution. 256This means that if, for example, the context is entered within an event 257handler subsequent event handlers will also run within that context unless 258specifically bound to another context with an `AsyncResource`. That is why 259`run()` should be preferred over `enterWith()` unless there are strong reasons 260to use the latter method. 261 262```js 263const store = { id: 1 }; 264 265emitter.on('my-event', () => { 266 asyncLocalStorage.enterWith(store); 267}); 268emitter.on('my-event', () => { 269 asyncLocalStorage.getStore(); // Returns the same object 270}); 271 272asyncLocalStorage.getStore(); // Returns undefined 273emitter.emit('my-event'); 274asyncLocalStorage.getStore(); // Returns the same object 275``` 276 277### `asyncLocalStorage.run(store, callback[, ...args])` 278 279<!-- YAML 280added: 281 - v13.10.0 282 - v12.17.0 283--> 284 285* `store` {any} 286* `callback` {Function} 287* `...args` {any} 288 289Runs a function synchronously within a context and returns its 290return value. The store is not accessible outside of the callback function. 291The store is accessible to any asynchronous operations created within the 292callback. 293 294The optional `args` are passed to the callback function. 295 296If the callback function throws an error, the error is thrown by `run()` too. 297The stacktrace is not impacted by this call and the context is exited. 298 299Example: 300 301```js 302const store = { id: 2 }; 303try { 304 asyncLocalStorage.run(store, () => { 305 asyncLocalStorage.getStore(); // Returns the store object 306 setTimeout(() => { 307 asyncLocalStorage.getStore(); // Returns the store object 308 }, 200); 309 throw new Error(); 310 }); 311} catch (e) { 312 asyncLocalStorage.getStore(); // Returns undefined 313 // The error will be caught here 314} 315``` 316 317### `asyncLocalStorage.exit(callback[, ...args])` 318 319<!-- YAML 320added: 321 - v13.10.0 322 - v12.17.0 323--> 324 325> Stability: 1 - Experimental 326 327* `callback` {Function} 328* `...args` {any} 329 330Runs a function synchronously outside of a context and returns its 331return value. The store is not accessible within the callback function or 332the asynchronous operations created within the callback. Any `getStore()` 333call done within the callback function will always return `undefined`. 334 335The optional `args` are passed to the callback function. 336 337If the callback function throws an error, the error is thrown by `exit()` too. 338The stacktrace is not impacted by this call and the context is re-entered. 339 340Example: 341 342```js 343// Within a call to run 344try { 345 asyncLocalStorage.getStore(); // Returns the store object or value 346 asyncLocalStorage.exit(() => { 347 asyncLocalStorage.getStore(); // Returns undefined 348 throw new Error(); 349 }); 350} catch (e) { 351 asyncLocalStorage.getStore(); // Returns the same object or value 352 // The error will be caught here 353} 354``` 355 356### Usage with `async/await` 357 358If, within an async function, only one `await` call is to run within a context, 359the following pattern should be used: 360 361```js 362async function fn() { 363 await asyncLocalStorage.run(new Map(), () => { 364 asyncLocalStorage.getStore().set('key', value); 365 return foo(); // The return value of foo will be awaited 366 }); 367} 368``` 369 370In this example, the store is only available in the callback function and the 371functions called by `foo`. Outside of `run`, calling `getStore` will return 372`undefined`. 373 374### Troubleshooting: Context loss 375 376In most cases, `AsyncLocalStorage` works without issues. In rare situations, the 377current store is lost in one of the asynchronous operations. 378 379If your code is callback-based, it is enough to promisify it with 380[`util.promisify()`][] so it starts working with native promises. 381 382If you need to use a callback-based API or your code assumes 383a custom thenable implementation, use the [`AsyncResource`][] class 384to associate the asynchronous operation with the correct execution context. 385Find the function call responsible for the context loss by logging the content 386of `asyncLocalStorage.getStore()` after the calls you suspect are responsible 387for the loss. When the code logs `undefined`, the last callback called is 388probably responsible for the context loss. 389 390## Class: `AsyncResource` 391 392<!-- YAML 393changes: 394 - version: v16.4.0 395 pr-url: https://github.com/nodejs/node/pull/37675 396 description: AsyncResource is now Stable. Previously, it had been Experimental. 397--> 398 399The class `AsyncResource` is designed to be extended by the embedder's async 400resources. Using this, users can easily trigger the lifetime events of their 401own resources. 402 403The `init` hook will trigger when an `AsyncResource` is instantiated. 404 405The following is an overview of the `AsyncResource` API. 406 407```mjs 408import { AsyncResource, executionAsyncId } from 'node:async_hooks'; 409 410// AsyncResource() is meant to be extended. Instantiating a 411// new AsyncResource() also triggers init. If triggerAsyncId is omitted then 412// async_hook.executionAsyncId() is used. 413const asyncResource = new AsyncResource( 414 type, { triggerAsyncId: executionAsyncId(), requireManualDestroy: false }, 415); 416 417// Run a function in the execution context of the resource. This will 418// * establish the context of the resource 419// * trigger the AsyncHooks before callbacks 420// * call the provided function `fn` with the supplied arguments 421// * trigger the AsyncHooks after callbacks 422// * restore the original execution context 423asyncResource.runInAsyncScope(fn, thisArg, ...args); 424 425// Call AsyncHooks destroy callbacks. 426asyncResource.emitDestroy(); 427 428// Return the unique ID assigned to the AsyncResource instance. 429asyncResource.asyncId(); 430 431// Return the trigger ID for the AsyncResource instance. 432asyncResource.triggerAsyncId(); 433``` 434 435```cjs 436const { AsyncResource, executionAsyncId } = require('node:async_hooks'); 437 438// AsyncResource() is meant to be extended. Instantiating a 439// new AsyncResource() also triggers init. If triggerAsyncId is omitted then 440// async_hook.executionAsyncId() is used. 441const asyncResource = new AsyncResource( 442 type, { triggerAsyncId: executionAsyncId(), requireManualDestroy: false }, 443); 444 445// Run a function in the execution context of the resource. This will 446// * establish the context of the resource 447// * trigger the AsyncHooks before callbacks 448// * call the provided function `fn` with the supplied arguments 449// * trigger the AsyncHooks after callbacks 450// * restore the original execution context 451asyncResource.runInAsyncScope(fn, thisArg, ...args); 452 453// Call AsyncHooks destroy callbacks. 454asyncResource.emitDestroy(); 455 456// Return the unique ID assigned to the AsyncResource instance. 457asyncResource.asyncId(); 458 459// Return the trigger ID for the AsyncResource instance. 460asyncResource.triggerAsyncId(); 461``` 462 463### `new AsyncResource(type[, options])` 464 465* `type` {string} The type of async event. 466* `options` {Object} 467 * `triggerAsyncId` {number} The ID of the execution context that created this 468 async event. **Default:** `executionAsyncId()`. 469 * `requireManualDestroy` {boolean} If set to `true`, disables `emitDestroy` 470 when the object is garbage collected. This usually does not need to be set 471 (even if `emitDestroy` is called manually), unless the resource's `asyncId` 472 is retrieved and the sensitive API's `emitDestroy` is called with it. 473 When set to `false`, the `emitDestroy` call on garbage collection 474 will only take place if there is at least one active `destroy` hook. 475 **Default:** `false`. 476 477Example usage: 478 479```js 480class DBQuery extends AsyncResource { 481 constructor(db) { 482 super('DBQuery'); 483 this.db = db; 484 } 485 486 getInfo(query, callback) { 487 this.db.get(query, (err, data) => { 488 this.runInAsyncScope(callback, null, err, data); 489 }); 490 } 491 492 close() { 493 this.db = null; 494 this.emitDestroy(); 495 } 496} 497``` 498 499### Static method: `AsyncResource.bind(fn[, type[, thisArg]])` 500 501<!-- YAML 502added: 503 - v14.8.0 504 - v12.19.0 505changes: 506 - version: v17.8.0 507 pr-url: https://github.com/nodejs/node/pull/42177 508 description: Changed the default when `thisArg` is undefined to use `this` 509 from the caller. 510 - version: v16.0.0 511 pr-url: https://github.com/nodejs/node/pull/36782 512 description: Added optional thisArg. 513--> 514 515* `fn` {Function} The function to bind to the current execution context. 516* `type` {string} An optional name to associate with the underlying 517 `AsyncResource`. 518* `thisArg` {any} 519 520Binds the given function to the current execution context. 521 522The returned function will have an `asyncResource` property referencing 523the `AsyncResource` to which the function is bound. 524 525### `asyncResource.bind(fn[, thisArg])` 526 527<!-- YAML 528added: 529 - v14.8.0 530 - v12.19.0 531changes: 532 - version: v17.8.0 533 pr-url: https://github.com/nodejs/node/pull/42177 534 description: Changed the default when `thisArg` is undefined to use `this` 535 from the caller. 536 - version: v16.0.0 537 pr-url: https://github.com/nodejs/node/pull/36782 538 description: Added optional thisArg. 539--> 540 541* `fn` {Function} The function to bind to the current `AsyncResource`. 542* `thisArg` {any} 543 544Binds the given function to execute to this `AsyncResource`'s scope. 545 546The returned function will have an `asyncResource` property referencing 547the `AsyncResource` to which the function is bound. 548 549### `asyncResource.runInAsyncScope(fn[, thisArg, ...args])` 550 551<!-- YAML 552added: v9.6.0 553--> 554 555* `fn` {Function} The function to call in the execution context of this async 556 resource. 557* `thisArg` {any} The receiver to be used for the function call. 558* `...args` {any} Optional arguments to pass to the function. 559 560Call the provided function with the provided arguments in the execution context 561of the async resource. This will establish the context, trigger the AsyncHooks 562before callbacks, call the function, trigger the AsyncHooks after callbacks, and 563then restore the original execution context. 564 565### `asyncResource.emitDestroy()` 566 567* Returns: {AsyncResource} A reference to `asyncResource`. 568 569Call all `destroy` hooks. This should only ever be called once. An error will 570be thrown if it is called more than once. This **must** be manually called. If 571the resource is left to be collected by the GC then the `destroy` hooks will 572never be called. 573 574### `asyncResource.asyncId()` 575 576* Returns: {number} The unique `asyncId` assigned to the resource. 577 578### `asyncResource.triggerAsyncId()` 579 580* Returns: {number} The same `triggerAsyncId` that is passed to the 581 `AsyncResource` constructor. 582 583<a id="async-resource-worker-pool"></a> 584 585### Using `AsyncResource` for a `Worker` thread pool 586 587The following example shows how to use the `AsyncResource` class to properly 588provide async tracking for a [`Worker`][] pool. Other resource pools, such as 589database connection pools, can follow a similar model. 590 591Assuming that the task is adding two numbers, using a file named 592`task_processor.js` with the following content: 593 594```mjs 595import { parentPort } from 'node:worker_threads'; 596parentPort.on('message', (task) => { 597 parentPort.postMessage(task.a + task.b); 598}); 599``` 600 601```cjs 602const { parentPort } = require('node:worker_threads'); 603parentPort.on('message', (task) => { 604 parentPort.postMessage(task.a + task.b); 605}); 606``` 607 608a Worker pool around it could use the following structure: 609 610```mjs 611import { AsyncResource } from 'node:async_hooks'; 612import { EventEmitter } from 'node:events'; 613import path from 'node:path'; 614import { Worker } from 'node:worker_threads'; 615 616const kTaskInfo = Symbol('kTaskInfo'); 617const kWorkerFreedEvent = Symbol('kWorkerFreedEvent'); 618 619class WorkerPoolTaskInfo extends AsyncResource { 620 constructor(callback) { 621 super('WorkerPoolTaskInfo'); 622 this.callback = callback; 623 } 624 625 done(err, result) { 626 this.runInAsyncScope(this.callback, null, err, result); 627 this.emitDestroy(); // `TaskInfo`s are used only once. 628 } 629} 630 631export default class WorkerPool extends EventEmitter { 632 constructor(numThreads) { 633 super(); 634 this.numThreads = numThreads; 635 this.workers = []; 636 this.freeWorkers = []; 637 this.tasks = []; 638 639 for (let i = 0; i < numThreads; i++) 640 this.addNewWorker(); 641 642 // Any time the kWorkerFreedEvent is emitted, dispatch 643 // the next task pending in the queue, if any. 644 this.on(kWorkerFreedEvent, () => { 645 if (this.tasks.length > 0) { 646 const { task, callback } = this.tasks.shift(); 647 this.runTask(task, callback); 648 } 649 }); 650 } 651 652 addNewWorker() { 653 const worker = new Worker(new URL('task_processor.js', import.meta.url)); 654 worker.on('message', (result) => { 655 // In case of success: Call the callback that was passed to `runTask`, 656 // remove the `TaskInfo` associated with the Worker, and mark it as free 657 // again. 658 worker[kTaskInfo].done(null, result); 659 worker[kTaskInfo] = null; 660 this.freeWorkers.push(worker); 661 this.emit(kWorkerFreedEvent); 662 }); 663 worker.on('error', (err) => { 664 // In case of an uncaught exception: Call the callback that was passed to 665 // `runTask` with the error. 666 if (worker[kTaskInfo]) 667 worker[kTaskInfo].done(err, null); 668 else 669 this.emit('error', err); 670 // Remove the worker from the list and start a new Worker to replace the 671 // current one. 672 this.workers.splice(this.workers.indexOf(worker), 1); 673 this.addNewWorker(); 674 }); 675 this.workers.push(worker); 676 this.freeWorkers.push(worker); 677 this.emit(kWorkerFreedEvent); 678 } 679 680 runTask(task, callback) { 681 if (this.freeWorkers.length === 0) { 682 // No free threads, wait until a worker thread becomes free. 683 this.tasks.push({ task, callback }); 684 return; 685 } 686 687 const worker = this.freeWorkers.pop(); 688 worker[kTaskInfo] = new WorkerPoolTaskInfo(callback); 689 worker.postMessage(task); 690 } 691 692 close() { 693 for (const worker of this.workers) worker.terminate(); 694 } 695} 696``` 697 698```cjs 699const { AsyncResource } = require('node:async_hooks'); 700const { EventEmitter } = require('node:events'); 701const path = require('node:path'); 702const { Worker } = require('node:worker_threads'); 703 704const kTaskInfo = Symbol('kTaskInfo'); 705const kWorkerFreedEvent = Symbol('kWorkerFreedEvent'); 706 707class WorkerPoolTaskInfo extends AsyncResource { 708 constructor(callback) { 709 super('WorkerPoolTaskInfo'); 710 this.callback = callback; 711 } 712 713 done(err, result) { 714 this.runInAsyncScope(this.callback, null, err, result); 715 this.emitDestroy(); // `TaskInfo`s are used only once. 716 } 717} 718 719class WorkerPool extends EventEmitter { 720 constructor(numThreads) { 721 super(); 722 this.numThreads = numThreads; 723 this.workers = []; 724 this.freeWorkers = []; 725 this.tasks = []; 726 727 for (let i = 0; i < numThreads; i++) 728 this.addNewWorker(); 729 730 // Any time the kWorkerFreedEvent is emitted, dispatch 731 // the next task pending in the queue, if any. 732 this.on(kWorkerFreedEvent, () => { 733 if (this.tasks.length > 0) { 734 const { task, callback } = this.tasks.shift(); 735 this.runTask(task, callback); 736 } 737 }); 738 } 739 740 addNewWorker() { 741 const worker = new Worker(path.resolve(__dirname, 'task_processor.js')); 742 worker.on('message', (result) => { 743 // In case of success: Call the callback that was passed to `runTask`, 744 // remove the `TaskInfo` associated with the Worker, and mark it as free 745 // again. 746 worker[kTaskInfo].done(null, result); 747 worker[kTaskInfo] = null; 748 this.freeWorkers.push(worker); 749 this.emit(kWorkerFreedEvent); 750 }); 751 worker.on('error', (err) => { 752 // In case of an uncaught exception: Call the callback that was passed to 753 // `runTask` with the error. 754 if (worker[kTaskInfo]) 755 worker[kTaskInfo].done(err, null); 756 else 757 this.emit('error', err); 758 // Remove the worker from the list and start a new Worker to replace the 759 // current one. 760 this.workers.splice(this.workers.indexOf(worker), 1); 761 this.addNewWorker(); 762 }); 763 this.workers.push(worker); 764 this.freeWorkers.push(worker); 765 this.emit(kWorkerFreedEvent); 766 } 767 768 runTask(task, callback) { 769 if (this.freeWorkers.length === 0) { 770 // No free threads, wait until a worker thread becomes free. 771 this.tasks.push({ task, callback }); 772 return; 773 } 774 775 const worker = this.freeWorkers.pop(); 776 worker[kTaskInfo] = new WorkerPoolTaskInfo(callback); 777 worker.postMessage(task); 778 } 779 780 close() { 781 for (const worker of this.workers) worker.terminate(); 782 } 783} 784 785module.exports = WorkerPool; 786``` 787 788Without the explicit tracking added by the `WorkerPoolTaskInfo` objects, 789it would appear that the callbacks are associated with the individual `Worker` 790objects. However, the creation of the `Worker`s is not associated with the 791creation of the tasks and does not provide information about when tasks 792were scheduled. 793 794This pool could be used as follows: 795 796```mjs 797import WorkerPool from './worker_pool.js'; 798import os from 'node:os'; 799 800const pool = new WorkerPool(os.availableParallelism()); 801 802let finished = 0; 803for (let i = 0; i < 10; i++) { 804 pool.runTask({ a: 42, b: 100 }, (err, result) => { 805 console.log(i, err, result); 806 if (++finished === 10) 807 pool.close(); 808 }); 809} 810``` 811 812```cjs 813const WorkerPool = require('./worker_pool.js'); 814const os = require('node:os'); 815 816const pool = new WorkerPool(os.availableParallelism()); 817 818let finished = 0; 819for (let i = 0; i < 10; i++) { 820 pool.runTask({ a: 42, b: 100 }, (err, result) => { 821 console.log(i, err, result); 822 if (++finished === 10) 823 pool.close(); 824 }); 825} 826``` 827 828### Integrating `AsyncResource` with `EventEmitter` 829 830Event listeners triggered by an [`EventEmitter`][] may be run in a different 831execution context than the one that was active when `eventEmitter.on()` was 832called. 833 834The following example shows how to use the `AsyncResource` class to properly 835associate an event listener with the correct execution context. The same 836approach can be applied to a [`Stream`][] or a similar event-driven class. 837 838```mjs 839import { createServer } from 'node:http'; 840import { AsyncResource, executionAsyncId } from 'node:async_hooks'; 841 842const server = createServer((req, res) => { 843 req.on('close', AsyncResource.bind(() => { 844 // Execution context is bound to the current outer scope. 845 })); 846 req.on('close', () => { 847 // Execution context is bound to the scope that caused 'close' to emit. 848 }); 849 res.end(); 850}).listen(3000); 851``` 852 853```cjs 854const { createServer } = require('node:http'); 855const { AsyncResource, executionAsyncId } = require('node:async_hooks'); 856 857const server = createServer((req, res) => { 858 req.on('close', AsyncResource.bind(() => { 859 // Execution context is bound to the current outer scope. 860 })); 861 req.on('close', () => { 862 // Execution context is bound to the scope that caused 'close' to emit. 863 }); 864 res.end(); 865}).listen(3000); 866``` 867 868[`AsyncResource`]: #class-asyncresource 869[`EventEmitter`]: events.md#class-eventemitter 870[`Stream`]: stream.md#stream 871[`Worker`]: worker_threads.md#class-worker 872[`util.promisify()`]: util.md#utilpromisifyoriginal 873