Nodejs обработка ошибок: различия между версиями
imported>Supportadmin |
imported>Supportadmin |
||
Строка 132: | Строка 132: | ||
<nowiki>var d = require('domain').create(); | <nowiki>var d = require('domain').create(); | ||
d.on('error', function(err){ | d.on('error', function(err){ | ||
// | // безопасная обработка ошибки | ||
console.log(err); | console.log(err); | ||
}); | }); | ||
// | // обработка непойманных ошибок в синхронном или асинхронном блоке кода | ||
d.run(function(){ | d.run(function(){ | ||
// the asynchronous or synchronous code that we want to catch thrown errors on | // the asynchronous or synchronous code that we want to catch thrown errors on |
Текущая версия от 12:07, 10 апреля 2014
Safely "throwing" errors
deally we'd like to avoid uncaught errors as much as possible, as such, instead of literally throwing the error, we can instead safely "throw" the error using one of the following methods depending on our code architecture:
For synchronous code
For synchronous code, if an error happens, return the error:
// Define divider as a syncrhonous function var divideSync = function(x,y) { // if error condition? if ( y === 0 ) { // "throw" the error safely by returning it return new Error("Can't divide by zero"); } else { // no error occured, continue on return x/y; } }; // Divide 4/2 var result; result = divideSync(4,2); // did an error occur? if ( result instanceof Error ) { // handle the error safely console.log('4/2=err', result); } else { // no error occured, continue on console.log('4/2='+result); } // Divide 4/0 result = divideSync(4,0); // did an error occur? if ( result instanceof Error ) { // handle the error safely console.log('4/0=err', result); } else { // no error occured, continue on console.log('4/0='+result); }
For callback-based (ie. asynchronous) code
For callback-based (ie. asynchronous) code, the first argument of the callback is err, if an error happens err is the error, if an error doesn't happen then err is null. Any other arguments follow the err argument:
var divide = function(x,y,next) { // if error condition? if ( y === 0 ) { // "throw" the error safely by calling the completion callback // with the first argument being the error next(new Error("Can't divide by zero")); } else { // no error occured, continue on next(null, x/y); } }; divide(4,2,function(err,result){ // did an error occur? if ( err ) { // handle the error safely console.log('4/2=err', err); } else { // no error occured, continue on console.log('4/2='+result); } }); divide(4,0,function(err,result){ // did an error occur? if ( err ) { // handle the error safely console.log('4/0=err', err); } else { // no error occured, continue on console.log('4/0='+result); } });
For eventful code
For eventful code, where the error may happen anywhere, instead of throwing the error, fire the error event instead:
// Definite our Divider Event Emitter var events = require('events'); var Divider = function(){ events.EventEmitter.call(this); }; require('util').inherits(Divider, events.EventEmitter); // Add the divide function Divider.prototype.divide = function(x,y){ // if error condition? if ( y === 0 ) { // "throw" the error safely by emitting it var err = new Error("Can't divide by zero"); this.emit('error', err); } else { // no error occured, continue on this.emit('divided', x, y, x/y); } // Chain return this; }; // Create our divider and listen for errors var divider = new Divider(); divider.on('error', function(err){ // handle the error safely console.log(err); }); divider.on('divided', function(x,y,result){ console.log(x+'/'+y+'='+result); }); // Divide divider.divide(4,2).divide(4,0);
Безопасная обработка ошибок
Иногда, может быть код, который выдает где-то ошибки, которые могут привести к неперехваченным исключениям и потенциальному краху нашего приложения, если мы безопасно не обработаем их. В зависимости от нашей архитектуры кода мы можем использовать один из следующих методов, чтобы обработать ошибку:
Когда мы знаем где может произойти ошибка, мы можем обернуть этот участок кода в node.js domain
var d = require('domain').create(); d.on('error', function(err){ // безопасная обработка ошибки console.log(err); }); // обработка непойманных ошибок в синхронном или асинхронном блоке кода d.run(function(){ // the asynchronous or synchronous code that we want to catch thrown errors on var err = new Error('example'); throw err; });
If we know where the error is occurring is synchronous code, and for whatever reason can't use domains (perhaps old version of node), we can use the try catch statement:
// catch the uncaught errors in this synchronous code block // try catch statements only work on synchronous code try { // the synchronous code that we want to catch thrown errors on var err = new Error('example'); throw err; } catch (err) { // handle the error safely console.log(err); }
However, there may still be a case where an uncaught error happens in a place that wasn't wrapped in a domain or a try catch statement, in which case to make our application not crash we can use the uncaughtException listener (however doing so can put the application in an unknown state):
// catch the uncaught errors that weren't wrapped in a domain or try catch statement // do not use this in modules, but only in applications, as otherwise we could have multiple of these bound process.on('uncaughtException', function(err) { // handle the error safely console.log(err); }); // the asynchronous or synchronous code that emits the otherwise uncaught error var err = new Error('example'); throw err;
Nodejs domains
nodejs domains is the most up to date way of handling errors in nodejs. Domains can caputure both error/other events as well as traditionally thrown objects. Domains also provide functionality for handling callbacks with an error passed as the first argument via the intercept method.
As with normal try/catch-style error handling, is is usually best to throw errors when they occur, and block out areas where you want to isolate errors from affecting the rest of the code. The way to "block out" these areas are to call domain.run with a function as a block of isolated code.
In synchronous code, the above is enough - when an error happens you either let it be thrown through, or you catch it and handle there, reverting any data you need to revert.
try { //something } catch(e) { // handle data reversion // probably log too }
When the error happens in an asynchronous callback, you either need to be able to fully handle the rollback of data (shared state, external data like databases, etc). OR you have to set something to indicate that an exception has happened - where ever you care about that flag, you have to wait for the callback to complete.
var err = null; var d = require('domain').create(); d.on('error', function(e) { err = e; // any additional error handling } d.run(function() { Fiber(function() { // do stuff var future = somethingAsynchronous(); // more stuff future.wait(); // here we care about the error if(err != null) { // handle data reversion // probably log too } })});
Some of that above code is ugly, but you can create patterns for yourself to make it prettier, eg:
var specialDomain = specialDomain(function() { // do stuff var future = somethingAsynchronous(); // more stuff future.wait(); // here we care about the error if(specialDomain.error()) { // handle data reversion // probably log too } }, function() { // "catch" // any additional error handling });
UPDATE (2013-09):
Above, I use a future that implies fibers semantics, which allow you to wait on futures in-line. This actually allows you to use traditional try-catch blocks for everything - which I find to be the best way to go. However, you can't always do this (ie in the browser)...
There are also futures that don't require fibers semantics (which then work with normal, browsery javascript). These can be called futures, promises, or deferreds (I'll just refer to futures from here on). Plain-old-javascript futures libraries allow errors to be propagated between futures. Only some of these libraries allow any thrown future to be correctly handled, so beware.
An example:
returnsAFuture().then(function() { console.log('1') return doSomething() // also returns a future }).then(function() { console.log('2') throw Error("oops an error was thrown") }).then(function() { console.log('3') }).catch(function(exception) { console.log('handler') // handle the exception }).done()
This mimics a normal try-catch, even though the pieces are asynchronous. It would print:
1 2 handler
Note that it doesn't print '3' because an exception was thrown that interrupts that flow.
Take a look at these libraries:
Note that I haven't found many other libraries other than these that properly handle thrown exceptions. Jquery's deferred, for example, don't - the "fail" handler would never get the exception thrown an a 'then' handler, which in my opinion is a deal breaker.