1# Domain 2 3<!-- YAML 4deprecated: v1.4.2 5changes: 6 - version: v8.8.0 7 pr-url: https://github.com/nodejs/node/pull/15695 8 description: Any `Promise`s created in VM contexts no longer have a 9 `.domain` property. Their handlers are still executed in the 10 proper domain, however, and `Promise`s created in the main 11 context still possess a `.domain` property. 12 - version: v8.0.0 13 pr-url: https://github.com/nodejs/node/pull/12489 14 description: Handlers for `Promise`s are now invoked in the domain in which 15 the first promise of a chain was created. 16--> 17 18<!--introduced_in=v0.10.0--> 19 20> Stability: 0 - Deprecated 21 22<!-- source_link=lib/domain.js --> 23 24**This module is pending deprecation.** Once a replacement API has been 25finalized, this module will be fully deprecated. Most developers should 26**not** have cause to use this module. Users who absolutely must have 27the functionality that domains provide may rely on it for the time being 28but should expect to have to migrate to a different solution 29in the future. 30 31Domains provide a way to handle multiple different IO operations as a 32single group. If any of the event emitters or callbacks registered to a 33domain emit an `'error'` event, or throw an error, then the domain object 34will be notified, rather than losing the context of the error in the 35`process.on('uncaughtException')` handler, or causing the program to 36exit immediately with an error code. 37 38## Warning: Don't ignore errors! 39 40<!-- type=misc --> 41 42Domain error handlers are not a substitute for closing down a 43process when an error occurs. 44 45By the very nature of how [`throw`][] works in JavaScript, there is almost 46never any way to safely "pick up where it left off", without leaking 47references, or creating some other sort of undefined brittle state. 48 49The safest way to respond to a thrown error is to shut down the 50process. Of course, in a normal web server, there may be many 51open connections, and it is not reasonable to abruptly shut those down 52because an error was triggered by someone else. 53 54The better approach is to send an error response to the request that 55triggered the error, while letting the others finish in their normal 56time, and stop listening for new requests in that worker. 57 58In this way, `domain` usage goes hand-in-hand with the cluster module, 59since the primary process can fork a new worker when a worker 60encounters an error. For Node.js programs that scale to multiple 61machines, the terminating proxy or service registry can take note of 62the failure, and react accordingly. 63 64For example, this is not a good idea: 65 66```js 67// XXX WARNING! BAD IDEA! 68 69const d = require('node:domain').create(); 70d.on('error', (er) => { 71 // The error won't crash the process, but what it does is worse! 72 // Though we've prevented abrupt process restarting, we are leaking 73 // a lot of resources if this ever happens. 74 // This is no better than process.on('uncaughtException')! 75 console.log(`error, but oh well ${er.message}`); 76}); 77d.run(() => { 78 require('node:http').createServer((req, res) => { 79 handleRequest(req, res); 80 }).listen(PORT); 81}); 82``` 83 84By using the context of a domain, and the resilience of separating our 85program into multiple worker processes, we can react more 86appropriately, and handle errors with much greater safety. 87 88```js 89// Much better! 90 91const cluster = require('node:cluster'); 92const PORT = +process.env.PORT || 1337; 93 94if (cluster.isPrimary) { 95 // A more realistic scenario would have more than 2 workers, 96 // and perhaps not put the primary and worker in the same file. 97 // 98 // It is also possible to get a bit fancier about logging, and 99 // implement whatever custom logic is needed to prevent DoS 100 // attacks and other bad behavior. 101 // 102 // See the options in the cluster documentation. 103 // 104 // The important thing is that the primary does very little, 105 // increasing our resilience to unexpected errors. 106 107 cluster.fork(); 108 cluster.fork(); 109 110 cluster.on('disconnect', (worker) => { 111 console.error('disconnect!'); 112 cluster.fork(); 113 }); 114 115} else { 116 // the worker 117 // 118 // This is where we put our bugs! 119 120 const domain = require('node:domain'); 121 122 // See the cluster documentation for more details about using 123 // worker processes to serve requests. How it works, caveats, etc. 124 125 const server = require('node:http').createServer((req, res) => { 126 const d = domain.create(); 127 d.on('error', (er) => { 128 console.error(`error ${er.stack}`); 129 130 // We're in dangerous territory! 131 // By definition, something unexpected occurred, 132 // which we probably didn't want. 133 // Anything can happen now! Be very careful! 134 135 try { 136 // Make sure we close down within 30 seconds 137 const killtimer = setTimeout(() => { 138 process.exit(1); 139 }, 30000); 140 // But don't keep the process open just for that! 141 killtimer.unref(); 142 143 // Stop taking new requests. 144 server.close(); 145 146 // Let the primary know we're dead. This will trigger a 147 // 'disconnect' in the cluster primary, and then it will fork 148 // a new worker. 149 cluster.worker.disconnect(); 150 151 // Try to send an error to the request that triggered the problem 152 res.statusCode = 500; 153 res.setHeader('content-type', 'text/plain'); 154 res.end('Oops, there was a problem!\n'); 155 } catch (er2) { 156 // Oh well, not much we can do at this point. 157 console.error(`Error sending 500! ${er2.stack}`); 158 } 159 }); 160 161 // Because req and res were created before this domain existed, 162 // we need to explicitly add them. 163 // See the explanation of implicit vs explicit binding below. 164 d.add(req); 165 d.add(res); 166 167 // Now run the handler function in the domain. 168 d.run(() => { 169 handleRequest(req, res); 170 }); 171 }); 172 server.listen(PORT); 173} 174 175// This part is not important. Just an example routing thing. 176// Put fancy application logic here. 177function handleRequest(req, res) { 178 switch (req.url) { 179 case '/error': 180 // We do some async stuff, and then... 181 setTimeout(() => { 182 // Whoops! 183 flerb.bark(); 184 }, timeout); 185 break; 186 default: 187 res.end('ok'); 188 } 189} 190``` 191 192## Additions to `Error` objects 193 194<!-- type=misc --> 195 196Any time an `Error` object is routed through a domain, a few extra fields 197are added to it. 198 199* `error.domain` The domain that first handled the error. 200* `error.domainEmitter` The event emitter that emitted an `'error'` event 201 with the error object. 202* `error.domainBound` The callback function which was bound to the 203 domain, and passed an error as its first argument. 204* `error.domainThrown` A boolean indicating whether the error was 205 thrown, emitted, or passed to a bound callback function. 206 207## Implicit binding 208 209<!--type=misc--> 210 211If domains are in use, then all **new** `EventEmitter` objects (including 212Stream objects, requests, responses, etc.) will be implicitly bound to 213the active domain at the time of their creation. 214 215Additionally, callbacks passed to low-level event loop requests (such as 216to `fs.open()`, or other callback-taking methods) will automatically be 217bound to the active domain. If they throw, then the domain will catch 218the error. 219 220In order to prevent excessive memory usage, `Domain` objects themselves 221are not implicitly added as children of the active domain. If they 222were, then it would be too easy to prevent request and response objects 223from being properly garbage collected. 224 225To nest `Domain` objects as children of a parent `Domain` they must be 226explicitly added. 227 228Implicit binding routes thrown errors and `'error'` events to the 229`Domain`'s `'error'` event, but does not register the `EventEmitter` on the 230`Domain`. 231Implicit binding only takes care of thrown errors and `'error'` events. 232 233## Explicit binding 234 235<!--type=misc--> 236 237Sometimes, the domain in use is not the one that ought to be used for a 238specific event emitter. Or, the event emitter could have been created 239in the context of one domain, but ought to instead be bound to some 240other domain. 241 242For example, there could be one domain in use for an HTTP server, but 243perhaps we would like to have a separate domain to use for each request. 244 245That is possible via explicit binding. 246 247```js 248// Create a top-level domain for the server 249const domain = require('node:domain'); 250const http = require('node:http'); 251const serverDomain = domain.create(); 252 253serverDomain.run(() => { 254 // Server is created in the scope of serverDomain 255 http.createServer((req, res) => { 256 // Req and res are also created in the scope of serverDomain 257 // however, we'd prefer to have a separate domain for each request. 258 // create it first thing, and add req and res to it. 259 const reqd = domain.create(); 260 reqd.add(req); 261 reqd.add(res); 262 reqd.on('error', (er) => { 263 console.error('Error', er, req.url); 264 try { 265 res.writeHead(500); 266 res.end('Error occurred, sorry.'); 267 } catch (er2) { 268 console.error('Error sending 500', er2, req.url); 269 } 270 }); 271 }).listen(1337); 272}); 273``` 274 275## `domain.create()` 276 277* Returns: {Domain} 278 279## Class: `Domain` 280 281* Extends: {EventEmitter} 282 283The `Domain` class encapsulates the functionality of routing errors and 284uncaught exceptions to the active `Domain` object. 285 286To handle the errors that it catches, listen to its `'error'` event. 287 288### `domain.members` 289 290* {Array} 291 292An array of timers and event emitters that have been explicitly added 293to the domain. 294 295### `domain.add(emitter)` 296 297* `emitter` {EventEmitter|Timer} emitter or timer to be added to the domain 298 299Explicitly adds an emitter to the domain. If any event handlers called by 300the emitter throw an error, or if the emitter emits an `'error'` event, it 301will be routed to the domain's `'error'` event, just like with implicit 302binding. 303 304This also works with timers that are returned from [`setInterval()`][] and 305[`setTimeout()`][]. If their callback function throws, it will be caught by 306the domain `'error'` handler. 307 308If the Timer or `EventEmitter` was already bound to a domain, it is removed 309from that one, and bound to this one instead. 310 311### `domain.bind(callback)` 312 313* `callback` {Function} The callback function 314* Returns: {Function} The bound function 315 316The returned function will be a wrapper around the supplied callback 317function. When the returned function is called, any errors that are 318thrown will be routed to the domain's `'error'` event. 319 320```js 321const d = domain.create(); 322 323function readSomeFile(filename, cb) { 324 fs.readFile(filename, 'utf8', d.bind((er, data) => { 325 // If this throws, it will also be passed to the domain. 326 return cb(er, data ? JSON.parse(data) : null); 327 })); 328} 329 330d.on('error', (er) => { 331 // An error occurred somewhere. If we throw it now, it will crash the program 332 // with the normal line number and stack message. 333}); 334``` 335 336### `domain.enter()` 337 338The `enter()` method is plumbing used by the `run()`, `bind()`, and 339`intercept()` methods to set the active domain. It sets `domain.active` and 340`process.domain` to the domain, and implicitly pushes the domain onto the domain 341stack managed by the domain module (see [`domain.exit()`][] for details on the 342domain stack). The call to `enter()` delimits the beginning of a chain of 343asynchronous calls and I/O operations bound to a domain. 344 345Calling `enter()` changes only the active domain, and does not alter the domain 346itself. `enter()` and `exit()` can be called an arbitrary number of times on a 347single domain. 348 349### `domain.exit()` 350 351The `exit()` method exits the current domain, popping it off the domain stack. 352Any time execution is going to switch to the context of a different chain of 353asynchronous calls, it's important to ensure that the current domain is exited. 354The call to `exit()` delimits either the end of or an interruption to the chain 355of asynchronous calls and I/O operations bound to a domain. 356 357If there are multiple, nested domains bound to the current execution context, 358`exit()` will exit any domains nested within this domain. 359 360Calling `exit()` changes only the active domain, and does not alter the domain 361itself. `enter()` and `exit()` can be called an arbitrary number of times on a 362single domain. 363 364### `domain.intercept(callback)` 365 366* `callback` {Function} The callback function 367* Returns: {Function} The intercepted function 368 369This method is almost identical to [`domain.bind(callback)`][]. However, in 370addition to catching thrown errors, it will also intercept [`Error`][] 371objects sent as the first argument to the function. 372 373In this way, the common `if (err) return callback(err);` pattern can be replaced 374with a single error handler in a single place. 375 376```js 377const d = domain.create(); 378 379function readSomeFile(filename, cb) { 380 fs.readFile(filename, 'utf8', d.intercept((data) => { 381 // Note, the first argument is never passed to the 382 // callback since it is assumed to be the 'Error' argument 383 // and thus intercepted by the domain. 384 385 // If this throws, it will also be passed to the domain 386 // so the error-handling logic can be moved to the 'error' 387 // event on the domain instead of being repeated throughout 388 // the program. 389 return cb(null, JSON.parse(data)); 390 })); 391} 392 393d.on('error', (er) => { 394 // An error occurred somewhere. If we throw it now, it will crash the program 395 // with the normal line number and stack message. 396}); 397``` 398 399### `domain.remove(emitter)` 400 401* `emitter` {EventEmitter|Timer} emitter or timer to be removed from the domain 402 403The opposite of [`domain.add(emitter)`][]. Removes domain handling from the 404specified emitter. 405 406### `domain.run(fn[, ...args])` 407 408* `fn` {Function} 409* `...args` {any} 410 411Run the supplied function in the context of the domain, implicitly 412binding all event emitters, timers, and low-level requests that are 413created in that context. Optionally, arguments can be passed to 414the function. 415 416This is the most basic way to use a domain. 417 418```js 419const domain = require('node:domain'); 420const fs = require('node:fs'); 421const d = domain.create(); 422d.on('error', (er) => { 423 console.error('Caught error!', er); 424}); 425d.run(() => { 426 process.nextTick(() => { 427 setTimeout(() => { // Simulating some various async stuff 428 fs.open('non-existent file', 'r', (er, fd) => { 429 if (er) throw er; 430 // proceed... 431 }); 432 }, 100); 433 }); 434}); 435``` 436 437In this example, the `d.on('error')` handler will be triggered, rather 438than crashing the program. 439 440## Domains and promises 441 442As of Node.js 8.0.0, the handlers of promises are run inside the domain in 443which the call to `.then()` or `.catch()` itself was made: 444 445```js 446const d1 = domain.create(); 447const d2 = domain.create(); 448 449let p; 450d1.run(() => { 451 p = Promise.resolve(42); 452}); 453 454d2.run(() => { 455 p.then((v) => { 456 // running in d2 457 }); 458}); 459``` 460 461A callback may be bound to a specific domain using [`domain.bind(callback)`][]: 462 463```js 464const d1 = domain.create(); 465const d2 = domain.create(); 466 467let p; 468d1.run(() => { 469 p = Promise.resolve(42); 470}); 471 472d2.run(() => { 473 p.then(p.domain.bind((v) => { 474 // running in d1 475 })); 476}); 477``` 478 479Domains will not interfere with the error handling mechanisms for 480promises. In other words, no `'error'` event will be emitted for unhandled 481`Promise` rejections. 482 483[`Error`]: errors.md#class-error 484[`domain.add(emitter)`]: #domainaddemitter 485[`domain.bind(callback)`]: #domainbindcallback 486[`domain.exit()`]: #domainexit 487[`setInterval()`]: timers.md#setintervalcallback-delay-args 488[`setTimeout()`]: timers.md#settimeoutcallback-delay-args 489[`throw`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/throw 490