Nodejs обработка ошибок

Материал из support.qbpro.ru

источник

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.