Deprecated since v1.4.2 - This module is pending deprecation. Once a replacement API has been finalized, this module will be fully deprecated.
Most developers should not have cause to use this module. Users who absolutely must have the functionality that domains provide may rely on it for the time being but should expect to have to migrate to a different solution in the future.
Overview
Domains provide a way to handle multiple different IO operations as a single group. If any of the event emitters or callbacks registered to a domain emit an 'error' event, or throw an error, then the domain object will be notified, rather than losing the context of the error in the process.on('uncaughtException') handler, or causing the program to exit immediately with an error code.
const domain = require('node:domain');
Don’t ignore errors! Domain error handlers are not a substitute for closing down a process when an error occurs.
By the very nature of how throw works in JavaScript, there is almost never any way to safely “pick up where it left off”, without leaking references, or creating some other sort of undefined brittle state.
The safest way to respond to a thrown error is to shut down the process. Of course, in a normal web server, there may be many open connections, and it is not reasonable to abruptly shut those down because an error was triggered by someone else.
The better approach is to send an error response to the request that triggered the error, while letting the others finish in their normal time, and stop listening for new requests in that worker.
Best Practices
Domain usage should go hand-in-hand with the cluster module, since the primary process can fork a new worker when a worker encounters an error.
const cluster = require('node:cluster');
const domain = require('node:domain');
const http = require('node:http');
if (cluster.isPrimary) {
cluster.fork();
cluster.fork();
cluster.on('disconnect', (worker) => {
console.error('disconnect!');
cluster.fork();
});
} else {
const server = http.createServer((req, res) => {
const d = domain.create();
d.on('error', (er) => {
console.error(`error ${er.stack}`);
try {
// Make sure we close down within 30 seconds
const killtimer = setTimeout(() => {
process.exit(1);
}, 30000);
killtimer.unref();
// Stop taking new requests
server.close();
// Let the primary know we're dead
cluster.worker.disconnect();
// Try to send an error to the request that triggered the problem
res.statusCode = 500;
res.setHeader('content-type', 'text/plain');
res.end('Oops, there was a problem!\n');
} catch (er2) {
console.error(`Error sending 500! ${er2.stack}`);
}
});
d.add(req);
d.add(res);
d.run(() => {
handleRequest(req, res);
});
});
server.listen(8080);
}
API
domain.create()
Creates a new Domain instance.
Returns: Domain
Class: Domain
The Domain class encapsulates the functionality of routing errors and uncaught exceptions to the active Domain object.
Extends: EventEmitter
Properties
domain.members
An array of event emitters that have been explicitly added to the domain.
Type: Array
Methods
domain.add(emitter)
Explicitly adds an emitter to the domain. If any event handlers called by the emitter throw an error, or if the emitter emits an 'error' event, it will be routed to the domain’s 'error' event, just like with implicit binding.
Parameters:
emitter (EventEmitter) - Emitter to be added to the domain
If the EventEmitter was already bound to a domain, it is removed from that one, and bound to this one instead.
domain.bind(callback)
Returns a wrapper function around the supplied callback function. When the returned function is called, any errors that are thrown will be routed to the domain’s 'error' event.
Parameters:
callback (Function) - The callback function
Returns: Function - The bound function
const d = domain.create();
function readSomeFile(filename, cb) {
fs.readFile(filename, 'utf8', d.bind((er, data) => {
// If this throws, it will also be passed to the domain
return cb(er, data ? JSON.parse(data) : null);
}));
}
d.on('error', (er) => {
// An error occurred somewhere
});
domain.enter()
Sets the active domain and pushes the domain onto the domain stack. Used by run(), bind(), and intercept() methods to set the active domain.
domain.exit()
Exits the current domain, popping it off the domain stack. Any time execution is going to switch to the context of a different chain of asynchronous calls, it’s important to ensure that the current domain is exited.
domain.intercept(callback)
Almost identical to domain.bind(callback). However, in addition to catching thrown errors, it will also intercept Error objects sent as the first argument to the function.
Parameters:
callback (Function) - The callback function
Returns: Function - The intercepted function
This allows the common if (err) return callback(err); pattern to be replaced with a single error handler in a single place.
const d = domain.create();
function readSomeFile(filename, cb) {
fs.readFile(filename, 'utf8', d.intercept((data) => {
// Note, the first argument is never passed to the callback
// since it is assumed to be the 'Error' argument
return cb(null, JSON.parse(data));
}));
}
d.on('error', (er) => {
// An error occurred somewhere
});
domain.remove(emitter)
The opposite of domain.add(emitter). Removes domain handling from the specified emitter.
Parameters:
emitter (EventEmitter) - Emitter to be removed from the domain
domain.run(fn[, …args])
Run the supplied function in the context of the domain, implicitly binding all event emitters, timers, and low-level requests that are created in that context. Optionally, arguments can be passed to the function.
Parameters:
fn (Function) - The function to run
...args (any) - Optional arguments to pass to the function
This is the most basic way to use a domain.
const domain = require('node:domain');
const fs = require('node:fs');
const d = domain.create();
d.on('error', (er) => {
console.error('Caught error!', er);
});
d.run(() => {
process.nextTick(() => {
setTimeout(() => {
fs.open('non-existent file', 'r', (er, fd) => {
if (er) throw er;
// proceed...
});
}, 100);
});
});
Error Object Additions
Any time an Error object is routed through a domain, a few extra fields are added to it:
error.domain - The domain that first handled the error
error.domainEmitter - The event emitter that emitted an 'error' event with the error object
error.domainBound - The callback function which was bound to the domain, and passed an error as its first argument
error.domainThrown - A boolean indicating whether the error was thrown, emitted, or passed to a bound callback function
Binding Types
Implicit Binding
If domains are in use, then all new EventEmitter objects (including Stream objects, requests, responses, etc.) will be implicitly bound to the active domain at the time of their creation.
Additionally, callbacks passed to low-level event loop requests (such as to fs.open(), or other callback-taking methods) will automatically be bound to the active domain. If they throw, then the domain will catch the error.
Implicit binding routes thrown errors and 'error' events to the Domain’s 'error' event, but does not register the EventEmitter on the Domain.
Explicit Binding
Sometimes, the domain in use is not the one that ought to be used for a specific event emitter. Or, the event emitter could have been created in the context of one domain, but ought to instead be bound to some other domain.
For example, there could be one domain in use for an HTTP server, but perhaps we would like to have a separate domain to use for each request. That is possible via explicit binding.
const domain = require('node:domain');
const http = require('node:http');
const serverDomain = domain.create();
serverDomain.run(() => {
http.createServer((req, res) => {
const reqd = domain.create();
reqd.add(req);
reqd.add(res);
reqd.on('error', (er) => {
console.error('Error', er, req.url);
try {
res.writeHead(500);
res.end('Error occurred, sorry.');
} catch (er2) {
console.error('Error sending 500', er2, req.url);
}
});
}).listen(1337);
});
Domains and Promises
As of Node.js 8.0.0, the handlers of promises are run inside the domain in which the call to .then() or .catch() itself was made:
const d1 = domain.create();
const d2 = domain.create();
let p;
d1.run(() => {
p = Promise.resolve(42);
});
d2.run(() => {
p.then((v) => {
// running in d2
});
});
A callback may be bound to a specific domain using domain.bind(callback):
const d1 = domain.create();
const d2 = domain.create();
let p;
d1.run(() => {
p = Promise.resolve(42);
});
d2.run(() => {
p.then(p.domain.bind((v) => {
// running in d1
}));
});
Domains will not interfere with the error handling mechanisms for promises. In other words, no 'error' event will be emitted for unhandled Promise rejections.