Manejo de Errores
El Manejo de Errores se refiere a cómo Express captura y procesa los errores que ocurren tanto de forma sincrónica como asincrónica. Express viene con un manejador de errores por defecto, por lo que no necesitas escribir el tuyo propio para empezar.
Captura de Errores
Es importante asegurarse de que Express capture todos los errores que ocurren mientras se ejecutan los manejadores de rutas y el middleware.
Los errores que ocurren en código sincrónico dentro de los manejadores de rutas y el middleware no requieren trabajo adicional. Si el código sincrónico lanza un error, Express lo capturará y procesará. Por ejemplo:
app.get('/', (req, res) => { throw new Error('BROKEN'); // Express will catch this on its own.});Para los errores devueltos por funciones asincrónicas invocadas por los manejadores de rutas
y el middleware, debes pasarlos a la función next(), donde Express los
capturará y procesará. Por ejemplo:
app.get('/', (req, res, next) => { fs.readFile('/file-does-not-exist', (err, data) => { if (err) { next(err); // Pass errors to Express. } else { res.send(data); } });});A partir de Express 5, los manejadores de rutas y el middleware que devuelven una Promise
llamarán automáticamente a next(value) cuando se rechacen o lancen un error.
Por ejemplo:
app.get('/user/:id', async (req, res, next) => { const user = await getUserById(req.params.id); res.send(user);});Si getUserById lanza un error o se rechaza, next será llamado con el
error lanzado o el valor rechazado. Si no se proporciona un valor de rechazo, next
será llamado con un objeto Error por defecto proporcionado por el router de Express.
Si pasas cualquier cosa a la función next() (excepto la cadena 'route'),
Express considera que la solicitud actual es un error y omitirá cualquier
función de enrutamiento y middleware restante que no sea de manejo de errores.
Si el callback en una secuencia no proporciona datos, solo errores, puedes simplificar este código de la siguiente manera:
app.get('/', [ function (req, res, next) { fs.writeFile('/inaccessible-path', 'data', next); }, function (req, res) { res.send('OK'); },]);En el ejemplo anterior, next se proporciona como callback para fs.writeFile,
que es llamado con o sin errores. Si no hay error, el segundo
manejador se ejecuta; de lo contrario, Express captura y procesa el error.
Debes capturar los errores que ocurren en código asincrónico invocado por los manejadores de rutas o el middleware y pasarlos a Express para su procesamiento. Por ejemplo:
app.get('/', (req, res, next) => { setTimeout(() => { try { throw new Error('BROKEN'); } catch (err) { next(err); } }, 100);});El ejemplo anterior utiliza un bloque try...catch para capturar errores en el
código asincrónico y pasarlos a Express. Si se omitiera el bloque try...catch,
Express no capturaría el error ya que no forma parte del código del manejador sincrónico.
Usa promises para evitar la sobrecarga del bloque try...catch o cuando uses funciones
que devuelven promises. Por ejemplo:
app.get('/', (req, res, next) => { Promise.resolve() .then(() => { throw new Error('BROKEN'); }) .catch(next); // Errors will be passed to Express.});Dado que las promises capturan automáticamente tanto errores sincrónicos como promises rechazadas,
puedes simplemente proporcionar next como el manejador catch final y Express capturará los errores,
ya que al manejador catch se le pasa el error como primer argumento.
También podrías usar una cadena de manejadores para confiar en la captura sincrónica de errores, reduciendo el código asincrónico a algo trivial. Por ejemplo:
app.get('/', [ function (req, res, next) { fs.readFile('/maybe-valid-file', 'utf-8', (err, data) => { res.locals.data = data; next(err); }); }, function (req, res) { res.locals.data = res.locals.data.split(',')[1]; res.send(res.locals.data); },]);El ejemplo anterior tiene un par de sentencias triviales de la llamada a readFile.
Si readFile causa un error, entonces pasa el error a Express; de lo contrario,
regresas rápidamente al mundo del manejo sincrónico de errores en el siguiente manejador
de la cadena. Luego, el ejemplo anterior intenta procesar los datos. Si esto falla, entonces el
manejador de errores sincrónico lo capturará. Si hubieras hecho este procesamiento dentro
del callback de readFile, la aplicación podría cerrarse y los manejadores de errores
de Express no se ejecutarían.
Sea cual sea el método que uses, si quieres que los manejadores de errores de Express sean llamados y que la aplicación sobreviva, debes asegurarte de que Express reciba el error.
El manejador de errores por defecto
Express viene con un manejador de errores integrado que se encarga de cualquier error que pueda encontrarse en la aplicación. Esta función de middleware de manejo de errores por defecto se añade al final de la pila de funciones de middleware.
Si pasas un error a next() y no lo manejas en un manejador de errores
personalizado, será manejado por el manejador de errores integrado; el error se
escribirá al cliente con el stack trace. El stack trace no se incluye
en el entorno de producción.
Nota
Establece la variable de entorno NODE_ENV a production, para ejecutar la aplicación en modo producción.
Cuando se escribe un error, la siguiente información se añade a la respuesta:
- El
res.statusCodese establece a partir deerr.status(oerr.statusCode). Si este valor está fuera del rango 4xx o 5xx, se establecerá a 500. - El
res.statusMessagese establece de acuerdo con el código de estado. - El cuerpo será el HTML del mensaje del código de estado cuando se encuentre en el entorno
de producción; de lo contrario, será
err.stack. - Cualquier header especificado en un objeto
err.headers.
Si llamas a next() con un error después de haber comenzado a escribir la
respuesta (por ejemplo, si encuentras un error mientras transmites la
respuesta al cliente), el manejador de errores por defecto de Express cierra la
conexión y falla la solicitud.
Por lo tanto, cuando añades un manejador de errores personalizado, debes delegar al manejador de errores por defecto de Express, cuando los headers ya se han enviado al cliente:
function errorHandler(err, req, res, next) { if (res.headersSent) { return next(err); } res.status(500); res.render('error', { error: err });}Ten en cuenta que el manejador de errores por defecto puede activarse si llamas a next() con un error
en tu código más de una vez, incluso si hay middleware de manejo de errores personalizado en su lugar.
Se puede encontrar otro middleware de manejo de errores en Express middleware.
Escritura de manejadores de errores
Define las funciones de middleware de manejo de errores de la misma manera que otras funciones de middleware,
excepto que las funciones de manejo de errores tienen cuatro argumentos en lugar de tres:
(err, req, res, next). Por ejemplo:
app.use((err, req, res, next) => { console.error(err.stack); res.status(500).send('Something broke!');});Defines el middleware de manejo de errores al final, después de otras llamadas a app.use() y rutas; por ejemplo:
const bodyParser = require('body-parser');const methodOverride = require('method-override');
app.use( bodyParser.urlencoded({ extended: true, }));app.use(bodyParser.json());app.use(methodOverride());app.use((err, req, res, next) => { // logic});import bodyParser from 'body-parser';import methodOverride from 'method-override';
app.use( bodyParser.urlencoded({ extended: true, }));app.use(bodyParser.json());app.use(methodOverride());app.use((err, req, res, next) => { // logic});Las respuestas desde una función de middleware pueden ser en cualquier formato, como una página de error HTML, un mensaje simple, o una cadena JSON.
Con fines organizativos (y de frameworks de mayor nivel), puedes definir
varias funciones de middleware de manejo de errores, tal como lo harías con
funciones de middleware regulares. Por ejemplo, para definir un manejador de errores
para solicitudes hechas usando XHR y aquellas sin él:
const bodyParser = require('body-parser');const methodOverride = require('method-override');
app.use( bodyParser.urlencoded({ extended: true, }));app.use(bodyParser.json());app.use(methodOverride());app.use(logErrors);app.use(clientErrorHandler);app.use(errorHandler);import bodyParser from 'body-parser';import methodOverride from 'method-override';
app.use( bodyParser.urlencoded({ extended: true, }));app.use(bodyParser.json());app.use(methodOverride());app.use(logErrors);app.use(clientErrorHandler);app.use(errorHandler);En este ejemplo, el logErrors genérico podría escribir información de la solicitud y
del error en stderr, por ejemplo:
function logErrors(err, req, res, next) { console.error(err.stack); next(err);}También en este ejemplo, clientErrorHandler se define de la siguiente manera; en este caso, el error se pasa explícitamente al siguiente manejador.
Ten en cuenta que cuando no se llama a “next” en una función de manejo de errores, eres responsable de escribir (y finalizar) la respuesta. De lo contrario, esas solicitudes se “colgarán” y no serán elegibles para la recolección de basura.
function clientErrorHandler(err, req, res, next) { if (req.xhr) { res.status(500).send({ error: 'Something failed!' }); } else { next(err); }}Implementa la función errorHandler "catch-all" de la siguiente manera (por ejemplo):
function errorHandler(err, req, res, next) { res.status(500); res.render('error', { error: err });}Si tienes un manejador de ruta con múltiples funciones de callback, puedes usar el parámetro route para saltar al siguiente manejador de ruta. Por ejemplo:
app.get( '/a_route_behind_paywall', (req, res, next) => { if (!req.user.hasPaid) { // continue handling this request next('route'); } else { next(); } }, (req, res, next) => { PaidContent.find((err, doc) => { if (err) return next(err); res.json(doc); }); });En este ejemplo, el manejador getPaidContent se omitirá, pero cualquier manejador restante en app para /a_route_behind_paywall continuaría ejecutándose.
Nota
Las llamadas a next() y next(err) indican que el manejador actual ha terminado y en qué estado.
next(err) omitirá todos los manejadores restantes en la cadena, excepto aquellos que están configurados para
manejar errores como se describió anteriormente.