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.
Errors in synchronous code
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.});Errors in asynchronous code
The recommended way to write asynchronous handlers is with async functions.
Route handlers and middleware that return a Promise call next(value)
automatically when they reject or throw an error, and async functions always
return a Promise, so their errors reach Express with no extra work. Por ejemplo:
app.get('/user/:id', async (req, res) => { const user = await getUserById(req.params.id); res.send(user);});import { type Request, type Response } from 'express';
app.get('/user/:id', async (req: Request, res: Response) => { const user = await getUserById(req.params.id); res.send(user);});Si getUserById arroja un error o rechaza, next será llamado con
el error arrojado o el valor rechazado. Si no se proporciona ningún valor rechazado, next
será llamado con un objeto de Error predeterminado proporcionado por el enrutador Express.
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.
Working with promise chains
If you build a promise chain instead of using an async function, return the
promise from the handler and Express will likewise call next automatically
when it rejects:
app.get('/', (req, res) => { return Promise.resolve().then(() => { throw new Error('BROKEN'); // Express will catch this and call next. });});import { type Request, type Response } from 'express';
app.get('/', (req: Request, res: Response) => { return Promise.resolve().then(() => { throw new Error('BROKEN'); // Express will catch this and call next. });});If the promise is not returned, Express does not know it exists, and you must
route the error yourself by providing next as the final catch handler.
Without it, the rejection would be unhandled and crash the process:
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.});This works because if a callback in the promise chain throws, the chain turns that exception into a rejection, which travels down to the final .catch. There, .catch calls its handler with the error as the first argument, which is exactly the argument next expects, so the error reaches Express.
Working with callback APIs
Errors produced by callback-based APIs, such as those in node:fs, are not
thrown and are not part of any promise. The callback receives them as its first
argument, and you must pass them to the next() function yourself, where
Express will catch and process them. 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); } });});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.
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.
Finally, for asynchronous code that provides no error-first callback, such as a timer, catch errors inside the asynchronous code itself and pass them to Express:
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.
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 { type Request, type Response, type NextFunction } from 'express';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: 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.