• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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