¿Principios de manejo de errores para aplicaciones Node.js + Express.js?

177

Parece que el informe / manejo de errores se realiza de manera diferente en las aplicaciones Node.js + Express.js en comparación con otros marcos. ¿Estoy en lo cierto al entender que funciona de la siguiente manera?

A) Detecte errores al recibirlos como parámetros para sus funciones de devolución de llamada. Por ejemplo:

doSomethingAndRunCallback(function(err) { 
    if(err) {  }
});

B) Informe de errores en MIDDLEWARE llamando a next (err). Ejemplo:

handleRequest(req, res, next) {
    // An error occurs…
    next(err);
}

C) Informar errores en RUTAS arrojando el error. Ejemplo:

app.get('/home', function(req, res) {
    // An error occurs
    throw err;
});

D) Maneje los errores configurando su propio manejador de errores a través de app.error () o use el manejador genérico de errores Connect. Ejemplo:

app.error(function(err, req, res, next) {
    console.error(err);
    res.send('Fail Whale, yo.');
});

¿Son estos cuatro principios la base para todo el manejo / informe de errores en las aplicaciones Node.js + Express.js?

Clint Harris
fuente

Respuestas:

183

El manejo de errores en Node.js es generalmente del formato A). La mayoría de las devoluciones de llamada devuelven un objeto de error como primer argumento o null.

Express.js usa middleware y la sintaxis de middleware usa B) y E) (mencionados a continuación).

C) es una mala práctica si me preguntas.

app.get('/home', function(req, res) {
    // An error occurs
    throw err;
});

Puede reescribir fácilmente lo anterior como

app.get('/home', function(req, res, next) {
    // An error occurs
    next(err);
});

La sintaxis de middleware es válida en una getsolicitud.

En cuanto a D)

(07:26:37 PM) tjholowaychuk: app.error se elimina en 3.x

TJ acaba de confirmar que app.errorestá en desuso a favor de E

MI)

app.use(function(err, req, res, next) {
  // Only handle `next(err)` calls
});

Cualquier middleware que tenga una longitud de 4 (4 argumentos) se considera middleware de error. Cuando una llamada se next(err)conecta, se conecta y llama a un middleware basado en errores.

Raynos
fuente
11
¡Gracias! Para cualquiera que pueda encontrar esto en el futuro, parece que el orden de los parámetros para el "método e" es en realidad err, req, res, next (en lugar de req, res, next, err).
Clint Harris
9
Esto se ve muy bien, pero un problema que veo es que algunos errores nunca llegan a los controladores de errores que describe, y solo pueden ser detectados por un controlador process.on ('uncaughtException', fn). La sabiduría convencional es dejar que eso suceda y confiar en Forever o similares para reiniciar la aplicación, pero si lo hace, ¿cómo devuelve una página de error amigable?
Paul
1
@chovy Además, solo para tu información. El controlador de errores debe entregarse a la aplicación después del error arrojado / siguiente. Si es antes, no detectará el error.
Lee Olayvar
3
next (err) es esencialmente la versión Express de lanzar un error, sin embargo
debes
1
@qodeninja Ese método se considera una práctica recomendada en Express.
David Oliveros
3

¿Por qué primer parámetro?

Debido a la naturaleza asincrónica de Node.js, el patrón de primer parámetro como err se ha establecido como una convención para el manejo de errores de Node.js de userland . Esto es porque asincrónico:

try {
    setTimeout(function() {
        throw 'something broke' //Some random error
    }, 5)
}
catch(e) {
   //Will never get caught
}

Por lo tanto, tener el primer argumento de la devolución de llamada es prácticamente la única forma sensata de pasar los errores de forma asincrónica que no sea simplemente lanzarlos.

Hacerlo resultará en algo unhandled exceptionque, tal como suena, implica que no se hizo nada para sacar la aplicación de su estado confuso.

Excepciones, ¿por qué existen?

Sin embargo, vale la pena señalar que prácticamente todas las partes de Node.js son emisores de eventos y el lanzamiento de una excepción es un evento de bajo nivel que se puede manejar como todos los eventos:

//This won't immediately crash if connection fails
var socket = require("net").createConnection(5000);
socket.on("error", function(err) {
    console.error("calm down...", err)
});

Esto puede, pero no debe, llevarse al extremo para detectar todos los errores y crear una aplicación que tratará de no bloquearse nunca. Esta es una idea terrible en casi todos los casos de uso, ya que dejará al desarrollador sin ninguna idea de lo que está sucediendo en el estado de la aplicación y es análogo al ajuste principal en try-catch.

Dominios - agrupando eventos lógicamente

Como parte de este problema de excepciones que hacen que las aplicaciones se caigan, los dominios permiten que el desarrollador tome, por ejemplo, la aplicación Express.js, e intente cerrar conexiones de manera sensata en caso de falla catastrófica.

ES6

Probablemente esté mencionando que esto cambiará nuevamente ya que ES6 permite que el patrón del generador cree eventos asincrónicos que todavía son atrapables con los bloques try / catch.

Koa (escrito por TJ Holowaychuck, el mismo autor original de Express.js) hace esto notablemente. Utiliza la yieldinstrucción ES6 para crear bloques que, aunque parecen casi sincrónicos, se manejan de la manera asíncrona del nodo habitual:

app.use(function *(next) {
    try {
        yield next;
    } 
    catch (err) {
        this.status = err.status || 500;
        this.body = err.message;
        this.app.emit('error', err, this);
    }
});

app.use(function *(next) {
    throw new Error('some error');
})

Este ejemplo fue robado descaradamente de aquí .

David
fuente