This article mainly introduces Node. js asynchronous Exception Handling and domain module parsing have some reference value. Interested friends can refer to this article to introduce Node. js asynchronous Exception Handling and domain module parsing are of reference value. If you are interested, refer
Asynchronous Exception Handling
Features of asynchronous exceptions
Due to the asynchronous callback feature of node, it is impossible to catch all exceptions through try catch:
try { process.nextTick(function () { foo.bar(); });} catch (err) { //can not catch it}
For web services, it is very hopeful:
// Express-style routing app. get ('/Index', function (req, res) {try {// business logic} catch (err) {logger. error (err); res. status Code = 500; return res. json ({success: false, message: 'server exception '});}});
If try catch can catch all exceptions, we can record errors in the Code and return a 500 error to the caller. Unfortunately, try catch cannot catch exceptions in asynchronous mode. So what we can do is:
App. get ('/Index', function (req, res) {// business logic}); process. on ('uncaughtexception ', function (err) {logger. error (err );});
At this time, although we can record the log of this error and the process will not exit unexpectedly, there is no way for us to return the requests that find the error in a friendly way, just to let it return timeout.
Domain
In node v0.8 +, a module domain is released. What this module does is what try catch cannot do: catch exceptions in asynchronous callback.
As a result, the helpless example above seems to have a solution:
Var domain = require ('domain '); // introduce the middleware of a domain, and package every request in an independent domain. // domain to handle the exception app. use (function (req, res, next) {var d = domain. create (); // listen to domain error events d. on ('error', function (err) {logger. error (err); res. status Code = 500; res. json ({sucess: false, messag: 'server exception'}); d. dispose () ;}); d. add (req); d. add (res); d. run (next) ;}); app. get ('/Index', function (req, res) {// process business });
We introduce domain in the form of middleware to handle exceptions in asynchronous mode. Of course, although the domain captures exceptions, the stack loss caused by exceptions may cause memory leakage. In this case, you need to restart the process, if you are interested, you can go to the domain middleware domain-middleware.
Weird failure
All our tests are normal. When we use them in the production environment, we find that domain suddenly becomes invalid! It does not capture exceptions in asynchronous mode, and eventually causes the process to exit unexpectedly. After some troubleshooting, it was found that redis was introduced to store sessions.
Var http = require ('http'); var connect = require ('connect '); var RedisStore = require ('connect-redis') (connect ); var domainMiddleware = require ('domain-middleware '); var server = http. createServer (); var app = connect (); app. use (connect. session ({key: 'key', secret: 'secret', store: new RedisStore (6379, 'localhost ')})); // for the usage of domainMiddleware, see the previous link app. use (domainMiddleware ({server: server, killTimeout: 30000 }));
At this time, when an exception occurs in our business logic code, it is found that it is not captured by domain! After some attempts, I finally located the problem:
Var domain = require ('domain '); var redis = require ('redis'); var cache = redis. createClient (6379, 'localhost'); function error () {cache. get ('A', function () {throw new Error ('something wrong ') ;});} function OK () {setTimeout (function () {throw new Error ('something wrong ') ;}, 100) ;}var d = domain. create (); d. on ('error', function (err) {console. log (err) ;}); d. run (OK); // domain caught exception d. run (error); // exception thrown
Strange! They are all asynchronous calls. why can't the former be captured while the latter?
Domain profiling
Looking back, let's take a look at what the domain has done to let us capture asynchronous requests (the Code comes from node v0.10.4, and this Part may be rapidly changing and optimizing ).
Node event Loop Mechanism
Before looking at the principle of Domain, we should first understand the two methods of nextTick and _ tickCallback.
function laterCall() { console.log('print me later');}process.nextTick(laterCallback);console.log('print me first');
Those who have written node in the above Code are familiar with it. The function of nextTick is to put laterCallback to the next event loop for execution. The _ tickCallback method is a non-public method. This method is called after the end of the current time loop to continue the next event loop.
In other words, node maintains a queue for the event loop, nextTick enters the queue, and _ tickCallback is listed.
Domain implementation
After learning about node's event Loop Mechanism, let's take a look at what domain has done.
Domain itself is actually an EventEmitter object, which transmits captured errors through events. In this way, we can simplify it to two points:
When to trigger the domain error event:
The process throws an exception and is not caught by any try catch. At this time, processFatal of the entire process will be triggered. If it is in the domain package, the error event will be triggered on the domain, otherwise, the uncaughtException event will be triggered on the process.
How to transmit a domain in multiple different event loops:
After the domain is instantiated, we usually call its run method (such as previously used in web Services) to execute a function in the package of this domain example. When the wrapped function is executed, the global variable process. domain will be directed to this domain instance. When an exception is thrown in this event loop to call processFatal, an error event is triggered on the domain if process. domain exists.
After require introduces the domain module, it will overwrite the global nextTick and _ tickCallback and inject some domain-related code:
// The simplified domain passes part of the code function nextDomainTick (callback) {nextTickQueue. push ({callback: callback, domain: process. domain});} function _ tickDomainCallback () {var tock = nextTickQueue. pop (); // set process. domain = tock. domain tock. domain & tock. domain. enter (); callback (); // clear process. domain tock. domain & tock. domain. exit ();}};
This is the key to transferring the domain in multiple event loops: record the current domain when nextTick joins the queue, when the event loop in the queue is started and executed by _ tickCallback, process the new event loop. domain is set to the previously recorded domain. In this way, no matter how process. nextTick is called in the Code encapsulated by domain, domain will be passed continuously.
Of course, there are two cases of node Asynchronization: one is the event form. Therefore, the EventEmitter constructor has the following code:
if (exports.usingDomains) { // if there is an active domain, then attach to it. domain = domain || require('domain'); if (domain.active && !(this instanceof domain.Domain)) { this.domain = domain.active; } }
When EventEmitter is instantiated, this object will be bound to the current domain. When an event on this object is triggered through emit, like when _ tickCallback is executed, the callback function will be again wrapped in the current domain.
Another case is setTimeout and setInterval. Similarly, in the source code of timer, we can also find such a code:
if (process.domain) timer.domain = process.domain;
Like EventEmmiter, these timer callback functions will also be wrapped in the current domain.
Node inserts domain code in three key locations, nextTick, timer, and event, so that they can be passed in different event loops.
More complex domain
In some cases, we may encounter the need for more complex domain usage.
Domain nesting: When the outer layer may have a domain, there will be other domains in the inner layer. The use scenario can be found in the document.
// create a top-level domain for the servervar 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);});
In order to implement this function, domain will secretly maintain a domain stack. If you are interested, you can see it here.
Solve Problems
Let's look back at the problem we just encountered: Why do the two seem to be the same asynchronous call, but one domain cannot catch exceptions? After understanding the principle, it is easy to think that the asynchronous call that calls redis is within the event loop that throws an error and is not within the scope of domain. Let's take a look at the problem through a shorter piece of code.
var domain = require('domain');var EventEmitter = require('events').EventEmitter;var e = new EventEmitter();var timer = setTimeout(function () { e.emit('data'); }, 10);function next() { e.once('data', function () { throw new Error('something wrong here'); });}var d = domain.create();d.on('error', function () { console.log('cache by domain');});d.run(next);
At this point, we also found that the error will not be captured by domain, and the reason is very clear: the two key objects timer and e are not within the domain range during initialization. Therefore, when the event monitored in the next function is triggered and the callback function that throws an exception is not in the domain package, the exception will not be caught by the domain!
In fact, node has designed an API specifically for this situation: domain. add. It can add timer and event objects outside the domain to the current domain. For the example above:
d.add(timer);//ord.add(e);
Add any timer or e object to the domain to capture the error.
Let's take a look at the problem that the domain cannot catch exceptions caused by redis at the beginning. Can we solve this problem?
In fact, there is still no way to achieve the best solution for this situation. When unexpected exceptions occur, we can only time out the current request, stop the process, and restart the process. The graceful module can work with cluster to implement this solution.
Domain is very powerful, but not omnipotent. I hope that after reading this article, you will be able to use domian correctly to avoid pitfalls.