Manejo de errores
Error Handling refers to how Express catches and processes errors that occur both synchronously and asynchronously. Express viene con un manejador de error predeterminado , así que no necesitas escribir el tuyo propio para empezar.
Errores de captura
Es importante asegurarse de que Express capture todos los errores que ocurren mientras ejecuta los manejadores de rutas y el middleware.
Los errores que ocurren en el código sincrónico dentro de los manejadores de rutas y middleware no requieren trabajo extra. Si el código sincrónico arroja un error, entonces Express hará capturarlo y procesarlo. Por ejemplo:
app.get('/', (req, res) => { throw new Error('BROKEN'); // Express will catch this on its own.});import { type Request, type Response } from 'express';
app.get('/', (req: Request, res: Response) => { 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 middleware, debes pasarlos a la función next(), donde Express las 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); } });});import { type Request, type Response, type NextFunction } from 'express';
app.get('/', (req: Request, res: Response, next: NextFunction) => { fs.readFile('/file-does-not-exist', (err, data) => { if (err) { next(err); // Pass errors to Express. } else { res.send(data); } });});Errors from rejected promises are not passed to next automatically, and this includes async functions: if an async route handler throws or awaits a rejected promise, the rejection is unhandled, which crashes the process on current Node.js versions. You must catch the error yourself and pass it to Express:
app.get('/user/:id', async (req, res, next) => { try { const user = await getUserById(req.params.id); res.send(user); } catch (err) { next(err); }});import { type Request, type Response, type NextFunction } from 'express';
app.get('/user/:id', async (req: Request, res: Response, next: NextFunction) => { try { const user = await getUserById(req.params.id); res.send(user); } catch (err) { next(err); }});Note
Consider updating to Express 5, where route handlers and middleware that
return a Promise call next(value) automatically when they reject or throw an error, making the
try...catch above unnecessary.
If you pass anything to the next() function (except the string 'route'),
Express regards the current request as being an error and will skip any
remaining non-error handling routing and middleware functions.
Si la devolución de llamada en una secuencia no proporciona datos, sólo errores, puede 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'); },]);import { type Request, type Response, type NextFunction } from 'express';
app.get('/', [ function (req: Request, res: Response, next: NextFunction) { fs.writeFile('/inaccessible-path', 'data', next); }, function (req: Request, res: Response) { res.send('OK'); },]);En el ejemplo anterior, next es proporcionado como el callback para fs.writeFile,
que es llamado con o sin errores. Si no hay error, se ejecuta el segundo manejador
, de lo contrario Express catches y procesa el error.
Debe capturar errores que ocurren en código asíncrono invocado por manejadores de ruta o un 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);});import { type Request, type Response, type NextFunction } from 'express';
app.get('/', (req: Request, res: Response, next: NextFunction) => { 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 el bloque try...catch
fuera omitido, Express no capturaría el error ya que no es parte del código de manejador
sincrónico.
Usa promesas para evitar la sobrecarga del bloque “intentar…atrapar” o al usar funciones que devuelven promesas. Por ejemplo:
app.get('/', (req, res, next) => { Promise.resolve() .then(() => { throw new Error('BROKEN'); }) .catch(next); // Errors will be passed to Express.});import { type Request, type Response, type NextFunction } from 'express';
app.get('/', (req: Request, res: Response, next: NextFunction) => { Promise.resolve() .then(() => { throw new Error('BROKEN'); }) .catch(next); // Errors will be passed to Express.});If a callback in the promise chain throws, the chain turns that exception into a rejection, which travels down to the final .catch. There, passing next as the handler is enough: .catch calls it with the error as its first argument, which is exactly the argument next expects, so the error reaches Express.
También podría utilizar una cadena de manejadores para depender de la captura de errores sincrónicos , 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); },]);import { type Request, type Response, type NextFunction } from 'express';
app.get('/', [ function (req: Request, res: Response, next: NextFunction) { fs.readFile('/maybe-valid-file', 'utf-8', (err, data) => { res.locals.data = data; next(err); }); }, function (req: Request, res: Response) { res.locals.data = res.locals.data.split(',')[1]; res.send(res.locals.data); },]);The above example contains a couple of trivial statements following the readFile
call. Si readFile causa un error, entonces pasa el error a Express, de lo contrario
regresa rápidamente al mundo del manejo sincrónico de errores en el siguiente manejador
en la cadena. Luego, el ejemplo anterior intenta procesar los datos. Si esto falla, entonces el gestor de errores sincrónico
lo capturará. Si hubiera hecho este procesamiento dentro de
el callback readFile, entonces la aplicación podría salir y los manejadores de error Express
no se ejecutarían.
Whichever method you use, if you want Express error handlers to be called in and the application to survive, you must ensure that Express receives the error.
El gestor de errores por defecto
Express viene con un gestor de errores integrado que se encarga de cualquier error que pueda encontrarse en la aplicación. Esta función middleware por defecto se añade al final de la pila de funciones de middleware.
Si pasas un error a next() y no lo gestionas en un controlador
error personalizado, será manejado por el gestor de errores integrado; el error será
escrito al cliente con el stack trace. El stack trace no está incluido
en el entorno de producción.
Note
Establece la variable de entorno NODE_ENV a production, para ejecutar la aplicación en el modo de producción.
When an error is written, the following information is added to the response:
- El
res.statusCodese establece desdeerr.status(oerr.statusCode). Si este valor está fuera del rango 4xx o 5xx, se establecerá a 500. - El
res.statusMessagese establece de acuerdo al código de estado. - El cuerpo será el HTML del mensaje del código de estado cuando en el entorno
de producción, de lo contrario será
err.stack. - Cualquier cabecera especificada en un objeto
err.headers.
Si llama a next() con un error después de haber comenzado a escribir la respuesta
(por ejemplo, si encuentras un error al transmitir la respuesta
al cliente), el gestor de errores Express por defecto cierra la conexión
y falla la solicitud.
Así que cuando añada un manejador de errores personalizado, debe delegar a el manejador de errores predeterminado Express cuando los encabezados ya han sido enviados al cliente:
function errorHandler(err, req, res, next) { if (res.headersSent) { return next(err); } res.status(500); res.render('error', { error: err });}import { type Request, type Response, type NextFunction } from 'express';
function errorHandler(err: Error, req: Request, res: Response, next: NextFunction) { if (res.headersSent) { return next(err); } res.status(500); res.render('error', { error: err });}Tenga en cuenta que el manejador de errores por defecto puede activarse si llama a next() con un error
en su código más de una vez, incluso si el middleware está configurado para manejar errores personalizados.
Otro middleware de manejo de error se puede encontrar en Express middleware.
Manejadores de errores escritos
Define las funciones de middleware de la misma manera que otras funciones de middleware,
excepto 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!');});import { type Request, type Response, type NextFunction } from 'express';
app.use((err: Error, req: Request, res: Response, next: NextFunction) => { console.error(err.stack); res.status(500).send('Something broke!');});Usted define middleware manejado por errores últimamente, después de otras app.use() y llamadas de 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});import bodyParser from 'body-parser';import methodOverride from 'method-override';import { type Request, type Response, type NextFunction } from 'express';
app.use( bodyParser.urlencoded({ extended: true, }));app.use(bodyParser.json());app.use(methodOverride());app.use((err: Error, req: Request, res: Response, next: NextFunction) => { // logic});Las respuestas desde dentro de una función middleware pueden estar en cualquier formato, como una página de error HTML, un mensaje simple, o una cadena JSON.
For organizational (and higher-level framework) purposes, you can define
several error-handling middleware functions, much as you would with
regular middleware functions. Por ejemplo, para definir un manejador de errores
para las solicitudes realizadas usando XHR y las que no tienen:
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 genérico logErrors podría escribir una solicitud y
información de error a stderr, por ejemplo:
function logErrors(err, req, res, next) { console.error(err.stack); next(err);}import { type Request, type Response, type NextFunction } from 'express';
function logErrors(err: Error, req: Request, res: Response, next: NextFunction) { 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.
Notice that when you do not call next in an error-handling function, you are responsible for writing (and ending) the response. 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); }}import { type Request, type Response, type NextFunction } from 'express';
function clientErrorHandler(err: Error, req: Request, res: Response, next: NextFunction) { if (req.xhr) { res.status(500).send({ error: 'Something failed!' }); } else { next(err); }}Implementa la función “catch-all” errorHandler como sigue (por ejemplo):
function errorHandler(err, req, res, next) { res.status(500); res.render('error', { error: err });}import { type Request, type Response, type NextFunction } from 'express';
function errorHandler(err: Error, req: Request, res: Response, next: NextFunction) { res.status(500); res.render('error', { error: err });}Si tiene un manejador de ruta con múltiples funciones de callback, puede utilizar 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); }); });import { type Request, type Response, type NextFunction } from 'express';
app.get( '/a_route_behind_paywall', (req: Request, res: Response, next: NextFunction) => { if (!req.user.hasPaid) { // continue handling this request next('route'); } else { next(); } }, (req: Request, res: Response, next: NextFunction) => { 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 siendo ejecutado.
Note
Las llamadas a next() y next(err) indican que el controlador actual está completo y en qué estado.
next(err) omitirá todos los manejadores restantes en la cadena, excepto aquellos que están configurados a
errores de manejo como se describe arriba.