1# Async hooks 2 3<!--introduced_in=v8.1.0--> 4 5> Stability: 1 - Experimental 6 7<!-- source_link=lib/async_hooks.js --> 8 9The `async_hooks` module provides an API to track asynchronous resources. It 10can be accessed using: 11 12```js 13const async_hooks = require('async_hooks'); 14``` 15 16## Terminology 17 18An asynchronous resource represents an object with an associated callback. 19This callback may be called multiple times, for example, the `'connection'` 20event in `net.createServer()`, or just a single time like in `fs.open()`. 21A resource can also be closed before the callback is called. `AsyncHook` does 22not explicitly distinguish between these different cases but will represent them 23as the abstract concept that is a resource. 24 25If [`Worker`][]s are used, each thread has an independent `async_hooks` 26interface, and each thread will use a new set of async IDs. 27 28## Public API 29 30### Overview 31 32Following is a simple overview of the public API. 33 34```js 35const async_hooks = require('async_hooks'); 36 37// Return the ID of the current execution context. 38const eid = async_hooks.executionAsyncId(); 39 40// Return the ID of the handle responsible for triggering the callback of the 41// current execution scope to call. 42const tid = async_hooks.triggerAsyncId(); 43 44// Create a new AsyncHook instance. All of these callbacks are optional. 45const asyncHook = 46 async_hooks.createHook({ init, before, after, destroy, promiseResolve }); 47 48// Allow callbacks of this AsyncHook instance to call. This is not an implicit 49// action after running the constructor, and must be explicitly run to begin 50// executing callbacks. 51asyncHook.enable(); 52 53// Disable listening for new asynchronous events. 54asyncHook.disable(); 55 56// 57// The following are the callbacks that can be passed to createHook(). 58// 59 60// init is called during object construction. The resource may not have 61// completed construction when this callback runs, therefore all fields of the 62// resource referenced by "asyncId" may not have been populated. 63function init(asyncId, type, triggerAsyncId, resource) { } 64 65// Before is called just before the resource's callback is called. It can be 66// called 0-N times for handles (e.g. TCPWrap), and will be called exactly 1 67// time for requests (e.g. FSReqCallback). 68function before(asyncId) { } 69 70// After is called just after the resource's callback has finished. 71function after(asyncId) { } 72 73// Destroy is called when the resource is destroyed. 74function destroy(asyncId) { } 75 76// promiseResolve is called only for promise resources, when the 77// `resolve` function passed to the `Promise` constructor is invoked 78// (either directly or through other means of resolving a promise). 79function promiseResolve(asyncId) { } 80``` 81 82#### `async_hooks.createHook(callbacks)` 83 84<!-- YAML 85added: v8.1.0 86--> 87 88* `callbacks` {Object} The [Hook Callbacks][] to register 89 * `init` {Function} The [`init` callback][]. 90 * `before` {Function} The [`before` callback][]. 91 * `after` {Function} The [`after` callback][]. 92 * `destroy` {Function} The [`destroy` callback][]. 93 * `promiseResolve` {Function} The [`promiseResolve` callback][]. 94* Returns: {AsyncHook} Instance used for disabling and enabling hooks 95 96Registers functions to be called for different lifetime events of each async 97operation. 98 99The callbacks `init()`/`before()`/`after()`/`destroy()` are called for the 100respective asynchronous event during a resource's lifetime. 101 102All callbacks are optional. For example, if only resource cleanup needs to 103be tracked, then only the `destroy` callback needs to be passed. The 104specifics of all functions that can be passed to `callbacks` is in the 105[Hook Callbacks][] section. 106 107```js 108const async_hooks = require('async_hooks'); 109 110const asyncHook = async_hooks.createHook({ 111 init(asyncId, type, triggerAsyncId, resource) { }, 112 destroy(asyncId) { } 113}); 114``` 115 116The callbacks will be inherited via the prototype chain: 117 118```js 119class MyAsyncCallbacks { 120 init(asyncId, type, triggerAsyncId, resource) { } 121 destroy(asyncId) {} 122} 123 124class MyAddedCallbacks extends MyAsyncCallbacks { 125 before(asyncId) { } 126 after(asyncId) { } 127} 128 129const asyncHook = async_hooks.createHook(new MyAddedCallbacks()); 130``` 131 132##### Error handling 133 134If any `AsyncHook` callbacks throw, the application will print the stack trace 135and exit. The exit path does follow that of an uncaught exception, but 136all `'uncaughtException'` listeners are removed, thus forcing the process to 137exit. The `'exit'` callbacks will still be called unless the application is run 138with `--abort-on-uncaught-exception`, in which case a stack trace will be 139printed and the application exits, leaving a core file. 140 141The reason for this error handling behavior is that these callbacks are running 142at potentially volatile points in an object's lifetime, for example during 143class construction and destruction. Because of this, it is deemed necessary to 144bring down the process quickly in order to prevent an unintentional abort in the 145future. This is subject to change in the future if a comprehensive analysis is 146performed to ensure an exception can follow the normal control flow without 147unintentional side effects. 148 149##### Printing in AsyncHooks callbacks 150 151Because printing to the console is an asynchronous operation, `console.log()` 152will cause the AsyncHooks callbacks to be called. Using `console.log()` or 153similar asynchronous operations inside an AsyncHooks callback function will thus 154cause an infinite recursion. An easy solution to this when debugging is to use a 155synchronous logging operation such as `fs.writeFileSync(file, msg, flag)`. 156This will print to the file and will not invoke AsyncHooks recursively because 157it is synchronous. 158 159```js 160const fs = require('fs'); 161const util = require('util'); 162 163function debug(...args) { 164 // Use a function like this one when debugging inside an AsyncHooks callback 165 fs.writeFileSync('log.out', `${util.format(...args)}\n`, { flag: 'a' }); 166} 167``` 168 169If an asynchronous operation is needed for logging, it is possible to keep 170track of what caused the asynchronous operation using the information 171provided by AsyncHooks itself. The logging should then be skipped when 172it was the logging itself that caused AsyncHooks callback to call. By 173doing this the otherwise infinite recursion is broken. 174 175### Class: `AsyncHook` 176 177The class `AsyncHook` exposes an interface for tracking lifetime events 178of asynchronous operations. 179 180#### `asyncHook.enable()` 181 182* Returns: {AsyncHook} A reference to `asyncHook`. 183 184Enable the callbacks for a given `AsyncHook` instance. If no callbacks are 185provided enabling is a noop. 186 187The `AsyncHook` instance is disabled by default. If the `AsyncHook` instance 188should be enabled immediately after creation, the following pattern can be used. 189 190```js 191const async_hooks = require('async_hooks'); 192 193const hook = async_hooks.createHook(callbacks).enable(); 194``` 195 196#### `asyncHook.disable()` 197 198* Returns: {AsyncHook} A reference to `asyncHook`. 199 200Disable the callbacks for a given `AsyncHook` instance from the global pool of 201`AsyncHook` callbacks to be executed. Once a hook has been disabled it will not 202be called again until enabled. 203 204For API consistency `disable()` also returns the `AsyncHook` instance. 205 206#### Hook callbacks 207 208Key events in the lifetime of asynchronous events have been categorized into 209four areas: instantiation, before/after the callback is called, and when the 210instance is destroyed. 211 212##### `init(asyncId, type, triggerAsyncId, resource)` 213 214* `asyncId` {number} A unique ID for the async resource. 215* `type` {string} The type of the async resource. 216* `triggerAsyncId` {number} The unique ID of the async resource in whose 217 execution context this async resource was created. 218* `resource` {Object} Reference to the resource representing the async 219 operation, needs to be released during _destroy_. 220 221Called when a class is constructed that has the _possibility_ to emit an 222asynchronous event. This _does not_ mean the instance must call 223`before`/`after` before `destroy` is called, only that the possibility 224exists. 225 226This behavior can be observed by doing something like opening a resource then 227closing it before the resource can be used. The following snippet demonstrates 228this. 229 230```js 231require('net').createServer().listen(function() { this.close(); }); 232// OR 233clearTimeout(setTimeout(() => {}, 10)); 234``` 235 236Every new resource is assigned an ID that is unique within the scope of the 237current Node.js instance. 238 239###### `type` 240 241The `type` is a string identifying the type of resource that caused 242`init` to be called. Generally, it will correspond to the name of the 243resource's constructor. 244 245```text 246FSEVENTWRAP, FSREQCALLBACK, GETADDRINFOREQWRAP, GETNAMEINFOREQWRAP, HTTPINCOMINGMESSAGE, 247HTTPCLIENTREQUEST, JSSTREAM, PIPECONNECTWRAP, PIPEWRAP, PROCESSWRAP, QUERYWRAP, 248SHUTDOWNWRAP, SIGNALWRAP, STATWATCHER, TCPCONNECTWRAP, TCPSERVERWRAP, TCPWRAP, 249TTYWRAP, UDPSENDWRAP, UDPWRAP, WRITEWRAP, ZLIB, SSLCONNECTION, PBKDF2REQUEST, 250RANDOMBYTESREQUEST, TLSWRAP, Microtask, Timeout, Immediate, TickObject 251``` 252 253There is also the `PROMISE` resource type, which is used to track `Promise` 254instances and asynchronous work scheduled by them. 255 256Users are able to define their own `type` when using the public embedder API. 257 258It is possible to have type name collisions. Embedders are encouraged to use 259unique prefixes, such as the npm package name, to prevent collisions when 260listening to the hooks. 261 262###### `triggerAsyncId` 263 264`triggerAsyncId` is the `asyncId` of the resource that caused (or "triggered") 265the new resource to initialize and that caused `init` to call. This is different 266from `async_hooks.executionAsyncId()` that only shows *when* a resource was 267created, while `triggerAsyncId` shows *why* a resource was created. 268 269The following is a simple demonstration of `triggerAsyncId`: 270 271```js 272async_hooks.createHook({ 273 init(asyncId, type, triggerAsyncId) { 274 const eid = async_hooks.executionAsyncId(); 275 fs.writeSync( 276 process.stdout.fd, 277 `${type}(${asyncId}): trigger: ${triggerAsyncId} execution: ${eid}\n`); 278 } 279}).enable(); 280 281require('net').createServer((conn) => {}).listen(8080); 282``` 283 284Output when hitting the server with `nc localhost 8080`: 285 286```console 287TCPSERVERWRAP(5): trigger: 1 execution: 1 288TCPWRAP(7): trigger: 5 execution: 0 289``` 290 291The `TCPSERVERWRAP` is the server which receives the connections. 292 293The `TCPWRAP` is the new connection from the client. When a new 294connection is made, the `TCPWrap` instance is immediately constructed. This 295happens outside of any JavaScript stack. (An `executionAsyncId()` of `0` means 296that it is being executed from C++ with no JavaScript stack above it.) With only 297that information, it would be impossible to link resources together in 298terms of what caused them to be created, so `triggerAsyncId` is given the task 299of propagating what resource is responsible for the new resource's existence. 300 301###### `resource` 302 303`resource` is an object that represents the actual async resource that has 304been initialized. This can contain useful information that can vary based on 305the value of `type`. For instance, for the `GETADDRINFOREQWRAP` resource type, 306`resource` provides the host name used when looking up the IP address for the 307host in `net.Server.listen()`. The API for accessing this information is 308not supported, but using the Embedder API, users can provide 309and document their own resource objects. For example, such a resource object 310could contain the SQL query being executed. 311 312In the case of Promises, the `resource` object will have an 313`isChainedPromise` property, set to `true` if the promise has a parent promise, 314and `false` otherwise. For example, in the case of `b = a.then(handler)`, `a` is 315considered a parent `Promise` of `b`. Here, `b` is considered a chained promise. 316 317In some cases the resource object is reused for performance reasons, it is 318thus not safe to use it as a key in a `WeakMap` or add properties to it. 319 320###### Asynchronous context example 321 322The following is an example with additional information about the calls to 323`init` between the `before` and `after` calls, specifically what the 324callback to `listen()` will look like. The output formatting is slightly more 325elaborate to make calling context easier to see. 326 327```js 328let indent = 0; 329async_hooks.createHook({ 330 init(asyncId, type, triggerAsyncId) { 331 const eid = async_hooks.executionAsyncId(); 332 const indentStr = ' '.repeat(indent); 333 fs.writeSync( 334 process.stdout.fd, 335 `${indentStr}${type}(${asyncId}):` + 336 ` trigger: ${triggerAsyncId} execution: ${eid}\n`); 337 }, 338 before(asyncId) { 339 const indentStr = ' '.repeat(indent); 340 fs.writeSync(process.stdout.fd, `${indentStr}before: ${asyncId}\n`); 341 indent += 2; 342 }, 343 after(asyncId) { 344 indent -= 2; 345 const indentStr = ' '.repeat(indent); 346 fs.writeSync(process.stdout.fd, `${indentStr}after: ${asyncId}\n`); 347 }, 348 destroy(asyncId) { 349 const indentStr = ' '.repeat(indent); 350 fs.writeSync(process.stdout.fd, `${indentStr}destroy: ${asyncId}\n`); 351 }, 352}).enable(); 353 354require('net').createServer(() => {}).listen(8080, () => { 355 // Let's wait 10ms before logging the server started. 356 setTimeout(() => { 357 console.log('>>>', async_hooks.executionAsyncId()); 358 }, 10); 359}); 360``` 361 362Output from only starting the server: 363 364```console 365TCPSERVERWRAP(5): trigger: 1 execution: 1 366TickObject(6): trigger: 5 execution: 1 367before: 6 368 Timeout(7): trigger: 6 execution: 6 369after: 6 370destroy: 6 371before: 7 372>>> 7 373 TickObject(8): trigger: 7 execution: 7 374after: 7 375before: 8 376after: 8 377``` 378 379As illustrated in the example, `executionAsyncId()` and `execution` each specify 380the value of the current execution context; which is delineated by calls to 381`before` and `after`. 382 383Only using `execution` to graph resource allocation results in the following: 384 385```console 386 root(1) 387 ^ 388 | 389TickObject(6) 390 ^ 391 | 392 Timeout(7) 393``` 394 395The `TCPSERVERWRAP` is not part of this graph, even though it was the reason for 396`console.log()` being called. This is because binding to a port without a host 397name is a *synchronous* operation, but to maintain a completely asynchronous 398API the user's callback is placed in a `process.nextTick()`. Which is why 399`TickObject` is present in the output and is a 'parent' for `.listen()` 400callback. 401 402The graph only shows *when* a resource was created, not *why*, so to track 403the *why* use `triggerAsyncId`. Which can be represented with the following 404graph: 405 406```console 407 bootstrap(1) 408 | 409 ˅ 410TCPSERVERWRAP(5) 411 | 412 ˅ 413 TickObject(6) 414 | 415 ˅ 416 Timeout(7) 417``` 418 419##### `before(asyncId)` 420 421* `asyncId` {number} 422 423When an asynchronous operation is initiated (such as a TCP server receiving a 424new connection) or completes (such as writing data to disk) a callback is 425called to notify the user. The `before` callback is called just before said 426callback is executed. `asyncId` is the unique identifier assigned to the 427resource about to execute the callback. 428 429The `before` callback will be called 0 to N times. The `before` callback 430will typically be called 0 times if the asynchronous operation was cancelled 431or, for example, if no connections are received by a TCP server. Persistent 432asynchronous resources like a TCP server will typically call the `before` 433callback multiple times, while other operations like `fs.open()` will call 434it only once. 435 436##### `after(asyncId)` 437 438* `asyncId` {number} 439 440Called immediately after the callback specified in `before` is completed. 441 442If an uncaught exception occurs during execution of the callback, then `after` 443will run *after* the `'uncaughtException'` event is emitted or a `domain`'s 444handler runs. 445 446##### `destroy(asyncId)` 447 448* `asyncId` {number} 449 450Called after the resource corresponding to `asyncId` is destroyed. It is also 451called asynchronously from the embedder API `emitDestroy()`. 452 453Some resources depend on garbage collection for cleanup, so if a reference is 454made to the `resource` object passed to `init` it is possible that `destroy` 455will never be called, causing a memory leak in the application. If the resource 456does not depend on garbage collection, then this will not be an issue. 457 458##### `promiseResolve(asyncId)` 459 460<!-- YAML 461added: v8.6.0 462--> 463 464* `asyncId` {number} 465 466Called when the `resolve` function passed to the `Promise` constructor is 467invoked (either directly or through other means of resolving a promise). 468 469`resolve()` does not do any observable synchronous work. 470 471The `Promise` is not necessarily fulfilled or rejected at this point if the 472`Promise` was resolved by assuming the state of another `Promise`. 473 474```js 475new Promise((resolve) => resolve(true)).then((a) => {}); 476``` 477 478calls the following callbacks: 479 480```text 481init for PROMISE with id 5, trigger id: 1 482 promise resolve 5 # corresponds to resolve(true) 483init for PROMISE with id 6, trigger id: 5 # the Promise returned by then() 484 before 6 # the then() callback is entered 485 promise resolve 6 # the then() callback resolves the promise by returning 486 after 6 487``` 488 489#### `async_hooks.executionAsyncResource()` 490 491<!-- YAML 492added: v12.17.0 493--> 494 495* Returns: {Object} The resource representing the current execution. 496 Useful to store data within the resource. 497 498Resource objects returned by `executionAsyncResource()` are most often internal 499Node.js handle objects with undocumented APIs. Using any functions or properties 500on the object is likely to crash your application and should be avoided. 501 502Using `executionAsyncResource()` in the top-level execution context will 503return an empty object as there is no handle or request object to use, 504but having an object representing the top-level can be helpful. 505 506```js 507const { open } = require('fs'); 508const { executionAsyncId, executionAsyncResource } = require('async_hooks'); 509 510console.log(executionAsyncId(), executionAsyncResource()); // 1 {} 511open(__filename, 'r', (err, fd) => { 512 console.log(executionAsyncId(), executionAsyncResource()); // 7 FSReqWrap 513}); 514``` 515 516This can be used to implement continuation local storage without the 517use of a tracking `Map` to store the metadata: 518 519```js 520const { createServer } = require('http'); 521const { 522 executionAsyncId, 523 executionAsyncResource, 524 createHook 525} = require('async_hooks'); 526const sym = Symbol('state'); // Private symbol to avoid pollution 527 528createHook({ 529 init(asyncId, type, triggerAsyncId, resource) { 530 const cr = executionAsyncResource(); 531 if (cr) { 532 resource[sym] = cr[sym]; 533 } 534 } 535}).enable(); 536 537const server = createServer((req, res) => { 538 executionAsyncResource()[sym] = { state: req.url }; 539 setTimeout(function() { 540 res.end(JSON.stringify(executionAsyncResource()[sym])); 541 }, 100); 542}).listen(3000); 543``` 544 545#### `async_hooks.executionAsyncId()` 546 547<!-- YAML 548added: v8.1.0 549changes: 550 - version: v8.2.0 551 pr-url: https://github.com/nodejs/node/pull/13490 552 description: Renamed from `currentId` 553--> 554 555* Returns: {number} The `asyncId` of the current execution context. Useful to 556 track when something calls. 557 558```js 559const async_hooks = require('async_hooks'); 560 561console.log(async_hooks.executionAsyncId()); // 1 - bootstrap 562fs.open(path, 'r', (err, fd) => { 563 console.log(async_hooks.executionAsyncId()); // 6 - open() 564}); 565``` 566 567The ID returned from `executionAsyncId()` is related to execution timing, not 568causality (which is covered by `triggerAsyncId()`): 569 570```js 571const server = net.createServer((conn) => { 572 // Returns the ID of the server, not of the new connection, because the 573 // callback runs in the execution scope of the server's MakeCallback(). 574 async_hooks.executionAsyncId(); 575 576}).listen(port, () => { 577 // Returns the ID of a TickObject (i.e. process.nextTick()) because all 578 // callbacks passed to .listen() are wrapped in a nextTick(). 579 async_hooks.executionAsyncId(); 580}); 581``` 582 583Promise contexts may not get precise `executionAsyncIds` by default. 584See the section on [promise execution tracking][]. 585 586#### `async_hooks.triggerAsyncId()` 587 588* Returns: {number} The ID of the resource responsible for calling the callback 589 that is currently being executed. 590 591```js 592const server = net.createServer((conn) => { 593 // The resource that caused (or triggered) this callback to be called 594 // was that of the new connection. Thus the return value of triggerAsyncId() 595 // is the asyncId of "conn". 596 async_hooks.triggerAsyncId(); 597 598}).listen(port, () => { 599 // Even though all callbacks passed to .listen() are wrapped in a nextTick() 600 // the callback itself exists because the call to the server's .listen() 601 // was made. So the return value would be the ID of the server. 602 async_hooks.triggerAsyncId(); 603}); 604``` 605 606Promise contexts may not get valid `triggerAsyncId`s by default. See 607the section on [promise execution tracking][]. 608 609## Promise execution tracking 610 611By default, promise executions are not assigned `asyncId`s due to the relatively 612expensive nature of the [promise introspection API][PromiseHooks] provided by 613V8. This means that programs using promises or `async`/`await` will not get 614correct execution and trigger ids for promise callback contexts by default. 615 616```js 617const ah = require('async_hooks'); 618Promise.resolve(1729).then(() => { 619 console.log(`eid ${ah.executionAsyncId()} tid ${ah.triggerAsyncId()}`); 620}); 621// produces: 622// eid 1 tid 0 623``` 624 625Observe that the `then()` callback claims to have executed in the context of the 626outer scope even though there was an asynchronous hop involved. Also, 627the `triggerAsyncId` value is `0`, which means that we are missing context about 628the resource that caused (triggered) the `then()` callback to be executed. 629 630Installing async hooks via `async_hooks.createHook` enables promise execution 631tracking: 632 633```js 634const ah = require('async_hooks'); 635ah.createHook({ init() {} }).enable(); // forces PromiseHooks to be enabled. 636Promise.resolve(1729).then(() => { 637 console.log(`eid ${ah.executionAsyncId()} tid ${ah.triggerAsyncId()}`); 638}); 639// produces: 640// eid 7 tid 6 641``` 642 643In this example, adding any actual hook function enabled the tracking of 644promises. There are two promises in the example above; the promise created by 645`Promise.resolve()` and the promise returned by the call to `then()`. In the 646example above, the first promise got the `asyncId` `6` and the latter got 647`asyncId` `7`. During the execution of the `then()` callback, we are executing 648in the context of promise with `asyncId` `7`. This promise was triggered by 649async resource `6`. 650 651Another subtlety with promises is that `before` and `after` callbacks are run 652only on chained promises. That means promises not created by `then()`/`catch()` 653will not have the `before` and `after` callbacks fired on them. For more details 654see the details of the V8 [PromiseHooks][] API. 655 656## JavaScript embedder API 657 658Library developers that handle their own asynchronous resources performing tasks 659like I/O, connection pooling, or managing callback queues may use the 660`AsyncResource` JavaScript API so that all the appropriate callbacks are called. 661 662### Class: `AsyncResource` 663 664The class `AsyncResource` is designed to be extended by the embedder's async 665resources. Using this, users can easily trigger the lifetime events of their 666own resources. 667 668The `init` hook will trigger when an `AsyncResource` is instantiated. 669 670The following is an overview of the `AsyncResource` API. 671 672```js 673const { AsyncResource, executionAsyncId } = require('async_hooks'); 674 675// AsyncResource() is meant to be extended. Instantiating a 676// new AsyncResource() also triggers init. If triggerAsyncId is omitted then 677// async_hook.executionAsyncId() is used. 678const asyncResource = new AsyncResource( 679 type, { triggerAsyncId: executionAsyncId(), requireManualDestroy: false } 680); 681 682// Run a function in the execution context of the resource. This will 683// * establish the context of the resource 684// * trigger the AsyncHooks before callbacks 685// * call the provided function `fn` with the supplied arguments 686// * trigger the AsyncHooks after callbacks 687// * restore the original execution context 688asyncResource.runInAsyncScope(fn, thisArg, ...args); 689 690// Call AsyncHooks destroy callbacks. 691asyncResource.emitDestroy(); 692 693// Return the unique ID assigned to the AsyncResource instance. 694asyncResource.asyncId(); 695 696// Return the trigger ID for the AsyncResource instance. 697asyncResource.triggerAsyncId(); 698``` 699 700#### `new AsyncResource(type[, options])` 701 702* `type` {string} The type of async event. 703* `options` {Object} 704 * `triggerAsyncId` {number} The ID of the execution context that created this 705 async event. **Default:** `executionAsyncId()`. 706 * `requireManualDestroy` {boolean} If set to `true`, disables `emitDestroy` 707 when the object is garbage collected. This usually does not need to be set 708 (even if `emitDestroy` is called manually), unless the resource's `asyncId` 709 is retrieved and the sensitive API's `emitDestroy` is called with it. 710 When set to `false`, the `emitDestroy` call on garbage collection 711 will only take place if there is at least one active `destroy` hook. 712 **Default:** `false`. 713 714Example usage: 715 716```js 717class DBQuery extends AsyncResource { 718 constructor(db) { 719 super('DBQuery'); 720 this.db = db; 721 } 722 723 getInfo(query, callback) { 724 this.db.get(query, (err, data) => { 725 this.runInAsyncScope(callback, null, err, data); 726 }); 727 } 728 729 close() { 730 this.db = null; 731 this.emitDestroy(); 732 } 733} 734``` 735 736#### Static method: `AsyncResource.bind(fn[, type])` 737<!-- YAML 738added: v12.19.0 739--> 740 741* `fn` {Function} The function to bind to the current execution context. 742* `type` {string} An optional name to associate with the underlying 743 `AsyncResource`. 744 745Binds the given function to the current execution context. 746 747The returned function will have an `asyncResource` property referencing 748the `AsyncResource` to which the function is bound. 749 750#### `asyncResource.bind(fn)` 751<!-- YAML 752added: v12.19.0 753--> 754 755* `fn` {Function} The function to bind to the current `AsyncResource`. 756 757Binds the given function to execute to this `AsyncResource`'s scope. 758 759The returned function will have an `asyncResource` property referencing 760the `AsyncResource` to which the function is bound. 761 762#### `asyncResource.runInAsyncScope(fn[, thisArg, ...args])` 763<!-- YAML 764added: v9.6.0 765--> 766 767* `fn` {Function} The function to call in the execution context of this async 768 resource. 769* `thisArg` {any} The receiver to be used for the function call. 770* `...args` {any} Optional arguments to pass to the function. 771 772Call the provided function with the provided arguments in the execution context 773of the async resource. This will establish the context, trigger the AsyncHooks 774before callbacks, call the function, trigger the AsyncHooks after callbacks, and 775then restore the original execution context. 776 777#### `asyncResource.emitDestroy()` 778 779* Returns: {AsyncResource} A reference to `asyncResource`. 780 781Call all `destroy` hooks. This should only ever be called once. An error will 782be thrown if it is called more than once. This **must** be manually called. If 783the resource is left to be collected by the GC then the `destroy` hooks will 784never be called. 785 786#### `asyncResource.asyncId()` 787 788* Returns: {number} The unique `asyncId` assigned to the resource. 789 790#### `asyncResource.triggerAsyncId()` 791 792* Returns: {number} The same `triggerAsyncId` that is passed to the 793`AsyncResource` constructor. 794 795<a id="async-resource-worker-pool"></a> 796### Using `AsyncResource` for a `Worker` thread pool 797 798The following example shows how to use the `AsyncResource` class to properly 799provide async tracking for a [`Worker`][] pool. Other resource pools, such as 800database connection pools, can follow a similar model. 801 802Assuming that the task is adding two numbers, using a file named 803`task_processor.js` with the following content: 804 805```js 806const { parentPort } = require('worker_threads'); 807parentPort.on('message', (task) => { 808 parentPort.postMessage(task.a + task.b); 809}); 810``` 811 812a Worker pool around it could use the following structure: 813 814```js 815const { AsyncResource } = require('async_hooks'); 816const { EventEmitter } = require('events'); 817const path = require('path'); 818const { Worker } = require('worker_threads'); 819 820const kTaskInfo = Symbol('kTaskInfo'); 821const kWorkerFreedEvent = Symbol('kWorkerFreedEvent'); 822 823class WorkerPoolTaskInfo extends AsyncResource { 824 constructor(callback) { 825 super('WorkerPoolTaskInfo'); 826 this.callback = callback; 827 } 828 829 done(err, result) { 830 this.runInAsyncScope(this.callback, null, err, result); 831 this.emitDestroy(); // `TaskInfo`s are used only once. 832 } 833} 834 835class WorkerPool extends EventEmitter { 836 constructor(numThreads) { 837 super(); 838 this.numThreads = numThreads; 839 this.workers = []; 840 this.freeWorkers = []; 841 842 for (let i = 0; i < numThreads; i++) 843 this.addNewWorker(); 844 } 845 846 addNewWorker() { 847 const worker = new Worker(path.resolve(__dirname, 'task_processor.js')); 848 worker.on('message', (result) => { 849 // In case of success: Call the callback that was passed to `runTask`, 850 // remove the `TaskInfo` associated with the Worker, and mark it as free 851 // again. 852 worker[kTaskInfo].done(null, result); 853 worker[kTaskInfo] = null; 854 this.freeWorkers.push(worker); 855 this.emit(kWorkerFreedEvent); 856 }); 857 worker.on('error', (err) => { 858 // In case of an uncaught exception: Call the callback that was passed to 859 // `runTask` with the error. 860 if (worker[kTaskInfo]) 861 worker[kTaskInfo].done(err, null); 862 else 863 this.emit('error', err); 864 // Remove the worker from the list and start a new Worker to replace the 865 // current one. 866 this.workers.splice(this.workers.indexOf(worker), 1); 867 this.addNewWorker(); 868 }); 869 this.workers.push(worker); 870 this.freeWorkers.push(worker); 871 this.emit(kWorkerFreedEvent); 872 } 873 874 runTask(task, callback) { 875 if (this.freeWorkers.length === 0) { 876 // No free threads, wait until a worker thread becomes free. 877 this.once(kWorkerFreedEvent, () => this.runTask(task, callback)); 878 return; 879 } 880 881 const worker = this.freeWorkers.pop(); 882 worker[kTaskInfo] = new WorkerPoolTaskInfo(callback); 883 worker.postMessage(task); 884 } 885 886 close() { 887 for (const worker of this.workers) worker.terminate(); 888 } 889} 890 891module.exports = WorkerPool; 892``` 893 894Without the explicit tracking added by the `WorkerPoolTaskInfo` objects, 895it would appear that the callbacks are associated with the individual `Worker` 896objects. However, the creation of the `Worker`s is not associated with the 897creation of the tasks and does not provide information about when tasks 898were scheduled. 899 900This pool could be used as follows: 901 902```js 903const WorkerPool = require('./worker_pool.js'); 904const os = require('os'); 905 906const pool = new WorkerPool(os.cpus().length); 907 908let finished = 0; 909for (let i = 0; i < 10; i++) { 910 pool.runTask({ a: 42, b: 100 }, (err, result) => { 911 console.log(i, err, result); 912 if (++finished === 10) 913 pool.close(); 914 }); 915} 916``` 917 918### Integrating `AsyncResource` with `EventEmitter` 919 920Event listeners triggered by an [`EventEmitter`][] may be run in a different 921execution context than the one that was active when `eventEmitter.on()` was 922called. 923 924The following example shows how to use the `AsyncResource` class to properly 925associate an event listener with the correct execution context. The same 926approach can be applied to a [`Stream`][] or a similar event-driven class. 927 928```js 929const { createServer } = require('http'); 930const { AsyncResource, executionAsyncId } = require('async_hooks'); 931 932const server = createServer((req, res) => { 933 req.on('close', AsyncResource.bind(() => { 934 // Execution context is bound to the current outer scope. 935 })); 936 req.on('close', () => { 937 // Execution context is bound to the scope that caused 'close' to emit. 938 }); 939 res.end(); 940}).listen(3000); 941``` 942 943## Class: `AsyncLocalStorage` 944<!-- YAML 945added: v12.17.0 946--> 947 948This class is used to create asynchronous state within callbacks and promise 949chains. It allows storing data throughout the lifetime of a web request 950or any other asynchronous duration. It is similar to thread-local storage 951in other languages. 952 953The following example uses `AsyncLocalStorage` to build a simple logger 954that assigns IDs to incoming HTTP requests and includes them in messages 955logged within each request. 956 957```js 958const http = require('http'); 959const { AsyncLocalStorage } = require('async_hooks'); 960 961const asyncLocalStorage = new AsyncLocalStorage(); 962 963function logWithId(msg) { 964 const id = asyncLocalStorage.getStore(); 965 console.log(`${id !== undefined ? id : '-'}:`, msg); 966} 967 968let idSeq = 0; 969http.createServer((req, res) => { 970 asyncLocalStorage.run(idSeq++, () => { 971 logWithId('start'); 972 // Imagine any chain of async operations here 973 setImmediate(() => { 974 logWithId('finish'); 975 res.end(); 976 }); 977 }); 978}).listen(8080); 979 980http.get('http://localhost:8080'); 981http.get('http://localhost:8080'); 982// Prints: 983// 0: start 984// 1: start 985// 0: finish 986// 1: finish 987``` 988 989When having multiple instances of `AsyncLocalStorage`, they are independent 990from each other. It is safe to instantiate this class multiple times. 991 992### `new AsyncLocalStorage()` 993<!-- YAML 994added: v12.17.0 995--> 996 997Creates a new instance of `AsyncLocalStorage`. Store is only provided within a 998`run` method call. 999 1000### `asyncLocalStorage.disable()` 1001<!-- YAML 1002added: v12.17.0 1003--> 1004 1005This method disables the instance of `AsyncLocalStorage`. All subsequent calls 1006to `asyncLocalStorage.getStore()` will return `undefined` until 1007`asyncLocalStorage.run()` is called again. 1008 1009When calling `asyncLocalStorage.disable()`, all current contexts linked to the 1010instance will be exited. 1011 1012Calling `asyncLocalStorage.disable()` is required before the 1013`asyncLocalStorage` can be garbage collected. This does not apply to stores 1014provided by the `asyncLocalStorage`, as those objects are garbage collected 1015along with the corresponding async resources. 1016 1017This method is to be used when the `asyncLocalStorage` is not in use anymore 1018in the current process. 1019 1020### `asyncLocalStorage.getStore()` 1021<!-- YAML 1022added: v12.17.0 1023--> 1024 1025* Returns: {any} 1026 1027This method returns the current store. 1028If this method is called outside of an asynchronous context initialized by 1029calling `asyncLocalStorage.run`, it will return `undefined`. 1030 1031### `asyncLocalStorage.enterWith(store)` 1032<!-- YAML 1033added: v12.17.0 1034--> 1035 1036* `store` {any} 1037 1038Calling `asyncLocalStorage.enterWith(store)` will transition into the context 1039for the remainder of the current synchronous execution and will persist 1040through any following asynchronous calls. 1041 1042Example: 1043 1044```js 1045const store = { id: 1 }; 1046asyncLocalStorage.enterWith(store); 1047asyncLocalStorage.getStore(); // Returns the store object 1048someAsyncOperation(() => { 1049 asyncLocalStorage.getStore(); // Returns the same object 1050}); 1051``` 1052 1053This transition will continue for the _entire_ synchronous execution. 1054This means that if, for example, the context is entered within an event 1055handler subsequent event handlers will also run within that context unless 1056specifically bound to another context with an `AsyncResource`. 1057 1058```js 1059const store = { id: 1 }; 1060 1061emitter.on('my-event', () => { 1062 asyncLocalStorage.enterWith(store); 1063}); 1064emitter.on('my-event', () => { 1065 asyncLocalStorage.getStore(); // Returns the same object 1066}); 1067 1068asyncLocalStorage.getStore(); // Returns undefined 1069emitter.emit('my-event'); 1070asyncLocalStorage.getStore(); // Returns the same object 1071``` 1072 1073### `asyncLocalStorage.run(store, callback[, ...args])` 1074<!-- YAML 1075added: v12.17.0 1076--> 1077 1078* `store` {any} 1079* `callback` {Function} 1080* `...args` {any} 1081 1082This methods runs a function synchronously within a context and return its 1083return value. The store is not accessible outside of the callback function or 1084the asynchronous operations created within the callback. 1085 1086Optionally, arguments can be passed to the function. They will be passed to 1087the callback function. 1088 1089If the callback function throws an error, it will be thrown by `run` too. 1090The stacktrace will not be impacted by this call and the context will 1091be exited. 1092 1093Example: 1094 1095```js 1096const store = { id: 2 }; 1097try { 1098 asyncLocalStorage.run(store, () => { 1099 asyncLocalStorage.getStore(); // Returns the store object 1100 throw new Error(); 1101 }); 1102} catch (e) { 1103 asyncLocalStorage.getStore(); // Returns undefined 1104 // The error will be caught here 1105} 1106``` 1107 1108### `asyncLocalStorage.exit(callback[, ...args])` 1109<!-- YAML 1110added: v12.17.0 1111--> 1112 1113* `callback` {Function} 1114* `...args` {any} 1115 1116This methods runs a function synchronously outside of a context and return its 1117return value. The store is not accessible within the callback function or 1118the asynchronous operations created within the callback. 1119 1120Optionally, arguments can be passed to the function. They will be passed to 1121the callback function. 1122 1123If the callback function throws an error, it will be thrown by `exit` too. 1124The stacktrace will not be impacted by this call and 1125the context will be re-entered. 1126 1127Example: 1128 1129```js 1130// Within a call to run 1131try { 1132 asyncLocalStorage.getStore(); // Returns the store object or value 1133 asyncLocalStorage.exit(() => { 1134 asyncLocalStorage.getStore(); // Returns undefined 1135 throw new Error(); 1136 }); 1137} catch (e) { 1138 asyncLocalStorage.getStore(); // Returns the same object or value 1139 // The error will be caught here 1140} 1141``` 1142 1143### Usage with `async/await` 1144 1145If, within an async function, only one `await` call is to run within a context, 1146the following pattern should be used: 1147 1148```js 1149async function fn() { 1150 await asyncLocalStorage.run(new Map(), () => { 1151 asyncLocalStorage.getStore().set('key', value); 1152 return foo(); // The return value of foo will be awaited 1153 }); 1154} 1155``` 1156 1157In this example, the store is only available in the callback function and the 1158functions called by `foo`. Outside of `run`, calling `getStore` will return 1159`undefined`. 1160 1161### Troubleshooting 1162 1163In most cases your application or library code should have no issues with 1164`AsyncLocalStorage`. But in rare cases you may face situations when the 1165current store is lost in one of asynchronous operations. In those cases, 1166consider the following options. 1167 1168If your code is callback-based, it is enough to promisify it with 1169[`util.promisify()`][], so it starts working with native promises. 1170 1171If you need to keep using callback-based API, or your code assumes 1172a custom thenable implementation, use the [`AsyncResource`][] class 1173to associate the asynchronous operation with the correct execution context. 1174 1175[`AsyncResource`]: #async_hooks_class_asyncresource 1176[`after` callback]: #async_hooks_after_asyncid 1177[`before` callback]: #async_hooks_before_asyncid 1178[`destroy` callback]: #async_hooks_destroy_asyncid 1179[`init` callback]: #async_hooks_init_asyncid_type_triggerasyncid_resource 1180[`promiseResolve` callback]: #async_hooks_promiseresolve_asyncid 1181[`EventEmitter`]: events.html#events_class_eventemitter 1182[Hook Callbacks]: #async_hooks_hook_callbacks 1183[PromiseHooks]: https://docs.google.com/document/d/1rda3yKGHimKIhg5YeoAmCOtyURgsbTH_qaYR79FELlk/edit 1184[`Stream`]: stream.html#stream_stream 1185[`Worker`]: worker_threads.html#worker_threads_class_worker 1186[promise execution tracking]: #async_hooks_promise_execution_tracking 1187[`util.promisify()`]: util.html#util_util_promisify_original 1188