May 10, 2021 Node.js
稳定性: 2 - 不稳定
Node.js domain contains methods that can think of different IO operations as separate groups.
If any of the events registered to
error
event, or throw an exception, the domain receives a notification instead of losing the context of the error in the
process.on('uncaughtException')
does not immediately exit the program with an error code.
You can't think of a domain error handler as an alternative to shutting down a process when an error occurs.
According to how thrown exceptions in JavaScript work, there is basically no way to safely "go back to where you left off" without revealing references or causing some other undefined state.
The safest way to respond to a throw error is to shut down the process. A normal server can have many active connections because it is obviously unreasonable for an error to close all connections.
A better approach is to send an error response to the request that triggered the error, and stop listening to the new request from the person who triggered the error while the other connections are working properly.
In this way,
域
domain and cluster modules can work together, and when a process encounters an error, the main process can replicate a new process.
For Node programs, terminal agents, or registered services, you can be aware of errors and react.
For example, the following code is not a good idea:
javascript
// XXX WARNING! BAD IDEA!
var d = require('domain').create();
d.on('error', function(er) {
// The error won't crash the process, but what it does is worse!
// Though we've prevented abrupt process restarting, we are leaking
// resources like crazy if this ever happens.
// This is no better than process.on('uncaughtException')!
console.log('error, but oh well', er.message);
});
d.run(function() {
require('http').createServer(function(req, res) {
handleRequest(req, res);
}).listen(PORT);
});
By using the context of the domain and cutting the program into multiple working processes, we can respond more rationally and handle errors more securely:
javascript
// 好一些的做法!
var cluster = require('cluster');
var PORT = +process.env.PORT || 1337;
if (cluster.isMaster) {
// In real life, you'd probably use more than just 2 workers,
// and perhaps not put the master and worker in the same file.
//
// You can also of course get a bit fancier about logging, and
// implement whatever custom logic you need to prevent DoS
// attacks and other bad behavior.
//
// See the options in the cluster documentation.
//
// The important thing is that the master does very little,
// increasing our resilience to unexpected errors.
cluster.fork();
cluster.fork();
cluster.on('disconnect', function(worker) {
console.error('disconnect!');
cluster.fork();
});
} else {
// the worker
//
// This is where we put our bugs!
var domain = require('domain');
// See the cluster documentation for more details about using
// worker processes to serve requests. How it works, caveats, etc.
var server = require('http').createServer(function(req, res) {
var d = domain.create();
d.on('error', function(er) {
console.error('error', er.stack);
// Note: we're in dangerous territory!
// By definition, something unexpected occurred,
// which we probably didn't want.
// Anything can happen now! Be very careful!
try {
// make sure we close down within 30 seconds
var killtimer = setTimeout(function() {
process.exit(1);
}, 30000);
// But don't keep the process open just for that!
killtimer.unref();
// stop taking new requests.
server.close();
// Let the master know we're dead. This will trigger a
// 'disconnect' in the cluster master, and then it will fork
// a new worker.
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) {
// oh well, not much we can do at this point.
console.error('Error sending 500!', er2.stack);
}
});
// Because req and res were created before this domain existed,
// we need to explicitly add them.
// See the explanation of implicit vs explicit binding below.
d.add(req);
d.add(res);
// Now run the handler function in the domain.
d.run(function() {
handleRequest(req, res);
});
});
server.listen(PORT);
}
// This part isn't important. Just an example routing thing.
// You'd put your fancy application logic here.
function handleRequest(req, res) {
switch(req.url) {
case '/error':
// We do some async stuff, and then...
setTimeout(function() {
// Whoops!
flerb.bark();
});
break;
default:
res.end('ok');
}
}
Whenever an error is routed to a domain, several fields are added.
error.domain
the first domain to handle errors
error.domainEmitter
this error object to trigger the event distributor for the 'error' event
error.domainBound
bound to domainain's callback function, and the first argument is error.
error.domainThrown
boolean value, indicating that the return function is thrown, distributed, or passed to the binding.
Newly distributed objects, including Stream objects, requests, responses, and so on, are implicitly bound to the domain currently in use.
In addition, callback functions passed to the underlying event loop, such as fs.open or other methods for receiving callbacks, are automatically bound to the domain. If they throw an exception, the domain catches the error message.
To avoid overuse of memory, domain objects are not implicitly added as child objects of a valid domain. If you do this, it can easily affect garbage collection of request and response objects.
If you want to embed domain objects as child objects into the parent domain, you must explicitly add them.
Implicitly binding routes throws
'error'
but does not register the event distributor to the
domain.dispose()
does not close the event distributor. I
mplicit binding only needs to be aware of thrown errors
'error'
events.
Sometimes the domain being used is not the domain of an event distributor. Or, the event distributor might be created in one domain, but bound to another.
For example, an HTTP server uses a positive domain object, but we want to be able to use a different domain for each request.
This can be done by explicit binding.
For example:
// create a top-level domain for the server
var serverDomain = domain.create();
serverDomain.run(function() {
// server is created in the scope of serverDomain
http.createServer(function(req, res) {
// req and res are also created in the scope of serverDomain
// however, we'd prefer to have a separate domain for each request.
// create it first thing, and add req and res to it.
var reqd = domain.create();
reqd.add(req);
reqd.add(res);
reqd.on('error', function(er) {
console.error('Error', er, req.url);
try {
res.writeHead(500);
res.end('Error occurred, sorry.');
} catch (er) {
console.error('Error sending 500', er, req.url);
}
});
}).listen(1337);
});
Used to return a new domain object.
This class encapsulates the ability to bring errors and unscathed exceptions to valid objects.
The domain
is a sub-class of EventEmitter.
L
isten for its
error
the caught error.
fn
{Function}
Running the provided function in the context of the domain implicitly binds all event distributors, timers, and underlying requests.
This is the basic way to use domains.
For example:
var d = domain.create();
d.on('error', function(er) {
console.error('Caught error!', er);
});
d.run(function() {
process.nextTick(function() {
setTimeout(function() { // simulating some various async stuff
fs.open('non-existent file', 'r', function(er, fd) {
if (er) throw er;
// proceed...
});
}, 100);
});
});
In this example, the program does not
d.on('error')
An array of timers and event distributors that are explicitly added to the domain.
emitter
{EventEmitter |
Timer's timers and event distributors that are added to the domain
Explicitly add a distributor to the domain. I
f the event handler called by the distributor throws an error, or the
error
it will direct
error
event, just like implicit binding.
The
setInterval
setTimeout
If these callback functions throw an error, they will be snapped by the domain's 'error' processor.
If the timer or distributor is already bound to the domain, it will be removed from the last domain and bound to the current domain.
emitter
{EventEmitter |
The distributor or timer to remove
In contrast to the domain.add function, which removes the distributor from the domain.
callback
callback function
The returned function is a wrapper function for the provided callback function.
When this returned function is called, all thrown errors are directed to the domain's
error
event.
var d = domain.create();
function readSomeFile(filename, cb) {
fs.readFile(filename, 'utf8', d.bind(function(er, data) {
// if this throws, it will also be passed to the domain
return cb(er, data ? JSON.parse(data) : null);
}));
}
d.on('error', function(er) {
// an error occurred somewhere.
// if we throw it now, it will crash the program
// with the normal line number and stack message.
});
callback
callback function
Similar
domain.bind(callback)
I
n addition to catching the thrown error, it also intercepts the Error object from being passed to the function as an argument.
In this way, the
if (er) return callback(er);
Patterns can be replaced by an error handling in one place.
var d = domain.create();
function readSomeFile(filename, cb) {
fs.readFile(filename, 'utf8', d.intercept(function(data) {
// note, the first argument is never passed to the
// callback since it is assumed to be the 'Error' argument
// and thus intercepted by the domain.
// if this throws, it will also be passed to the domain
// so the error-handling logic can be moved to the 'error'
// event on the domain instead of being repeated throughout
// the program.
return cb(null, JSON.parse(data));
}));
}
d.on('error', function(er) {
// an error occurred somewhere.
// if we throw it now, it will crash the program
// with the normal line number and stack message.
});
This function is like
run
system
bind
and
intercept
which sets a valid domain. I
t sets the
domain.active
process.domain
implicitly pushes the domain to the domain stack managed by the domain module
domain.exit()
The call of the enter function separates the asynchronous call chain from the end or interruption of the I/O operation bound to a domain.
Calling
enter
only the active domain, not the domain itself.
Enter and exit can be called as many times as
Enter
a separate
exit
exit
function exits the current domain and is removed from the domain's stack. W
henever the program's execution process is to switch to a different asynchronous call chain, make sure to exit the current domain.
Call the exit function, separate the asynchronous call chain, and bind to the end or break of an I/O operation bound to a domain.
If more than one nested domain is bound to the current context,
exit
function exits all nesting.
Calling
exit
only the active domain, not its own.
Enter and exit can be called as many times as
Enter
a separate
exit
If the exit is already set under this domain name, exit will not exit the domain return.
稳定性: 0 - 抛弃。通过域里设置的错误事件来显示的消除失败的 IO 操作。
After
dispos
is called, callback functions bound to the domain through run, bind, or intercept no longer use the domain and distribute the
dispose
event.