Gestione Degli Errori
Error Handling si riferisce a come Express cattura ed elabora gli errori che si verificano in modo sincrono e asincrono. Express viene fornito con un gestore di errore predefinito quindi non è necessario scrivere il proprio per iniziare.
Errori Di Cattura
È importante garantire che Express colmi tutti gli errori che si verificano mentre gestisce le rotte e middleware.
Errors in synchronous code
Gli errori che si verificano nel codice sincrono all’interno dei gestori del percorso e del middleware non richiedono lavoro aggiuntivo. Se il codice sincrono lancia un errore, allora Express catturerà ed elaborerà esso. Per esempio:
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. Per esempio:
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);});Se getUserById genera un errore o rifiuta, next verrà chiamato con
l’errore generato o il valore rifiutato. Se non viene fornito alcun valore rifiutato, next
verrà chiamato con un oggetto di errore predefinito fornito dal router Express.
Se passi qualcosa alla funzione next() (tranne la stringa 'route'),
Express considera la richiesta corrente come un errore e salterà qualsiasi
rimanenti funzioni di routing e middleware senza errori.
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. Per esempio:
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); } });});Se il callback in una sequenza non fornisce dati, solo errori, è possibile semplificare questo codice come segue:
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'); },]);Nell’esempio precedente, next è fornito come callback per fs.writeFile,
che viene chiamato con o senza errori. Se non c’è errore, viene eseguito il secondo gestore
, altrimenti Express cattura ed elabora l’errore.
Si potrebbe anche utilizzare una catena di gestori per fare affidamento su errore sincrono catching, riducendo il codice asincrono a qualcosa di banale. Per esempio:
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. Se readFile causa un errore, allora passa l’errore a Express, altrimenti
torna rapidamente al mondo della gestione sincrona degli errori nel prossimo gestore
nella catena. Poi, l’esempio di cui sopra cerca di elaborare i dati. Se questo fallisce, il gestore di errori sincroni
lo catturerà. Se avessi fatto questa elaborazione all’interno di
il callback readFile, l’applicazione potrebbe uscire e i gestori dell’errore Express
non sarebbero stati eseguiti.
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);});L’esempio precedente utilizza un blocco try...catch per catturare errori nel codice asincrono
e passarli a Express. Se il blocco prova...catch
fosse omesso, Express non coglierebbe l’errore dal momento che non fa parte del codice di gestione
sincrono.
Qualunque metodo si utilizzi, se si desidera che i gestori di errori Express siano chiamati dentro e l’applicazione per sopravvivere, è necessario assicurarsi che Express riceva l’errore.
Il gestore degli errori predefinito
Express è dotato di un gestore di errori integrato che si occupa di eventuali errori che potrebbero essere riscontrati nell’app. Questa funzione middleware di gestione degli errori predefinita viene aggiunta alla fine dello stack della funzione middleware.
Se passi un errore a next() e non lo gestisci in un gestore di errore
personalizzato, sarà gestito dal gestore di errori integrato; l’errore sarà
scritto al client con la traccia dello stack. La traccia dello stack non è inclusa
nell’ambiente di produzione.
Note
Imposta la variabile d’ambiente NODE_ENV su production, per eseguire l’applicazione in modalità di produzione.
Quando viene scritto un errore, alla risposta vengono aggiunte le seguenti informazioni:
- Il
res.statusCodeè impostato daerr.status(oerr.statusCode). Se questo valore è al di fuori dell’intervallo 4xx o 5xx, sarà impostato a 500. - Il
res.statusMessageè impostato in base al codice di stato. - Il corpo sarà l’HTML del messaggio del codice di stato quando in ambiente di produzione
, altrimenti sarà
err.stack. - Qualsiasi intestazione specificata in un oggetto
err.headers.
Se chiami next() con un errore dopo aver iniziato a scrivere la risposta
(ad esempio, se riscontri un errore durante lo streaming della risposta
al client), il gestore di errore predefinito Express chiude la connessione
e non riesce la richiesta.
Quindi, quando si aggiunge un gestore di errori personalizzato, è necessario delegare a il gestore di errori Express predefinito, quando le intestazioni sono già state inviate al client:
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 });}Nota che il gestore di errori predefinito può essere attivato se chiami next() con un errore
nel tuo codice più di una volta, anche se la gestione degli errori personalizzati è in funzione.
Altri errori di gestione middleware possono essere trovati su Express middleware.
Gestione errori di scrittura
Definire le funzioni middleware per la gestione degli errori allo stesso modo di altre funzioni middleware,
eccetto le funzioni di gestione degli errori hanno quattro argomenti invece di tre:
(err, req, res, next). Per esempio:
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!');});Definisci l’ultima gestione degli errori del middleware, dopo altre chiamate app.use() e percorsi; per esempio:
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});Le risposte da all’interno di una funzione middleware possono essere in qualsiasi formato, come una pagina di errore HTML, un semplice messaggio, o una stringa JSON.
Per scopi organizzativi (e di framework di livello superiore) è possibile definire diverse funzioni middleware di gestione degli errori
come si farebbe con funzioni di middleware regolari
. Ad esempio, per definire un gestore di errori
per le richieste fatte utilizzando XHR e quelle senza:
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);In questo esempio, i logErrors generici potrebbero scrivere la richiesta e le informazioni di errore
in stderr, per esempio:
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);}Anche in questo esempio, clientErrorHandler è definito come segue; in questo caso, l’errore viene esplicitamente passato a quello successivo.
Notice that when you do not call next in an error-handling function, you are responsible for writing (and ending) the response. In caso contrario, tali richieste saranno “appese” e non saranno ammissibili per la raccolta rifiuti.
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 funzione errorHandler “catch-all” come segue (per esempio):
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 });}Se si dispone di un gestore del percorso con molteplici funzioni di callback, è possibile utilizzare il parametro route per passare al prossimo gestore del percorso. Per esempio:
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); }); });In questo esempio, il gestore getPaidContent sarà saltato, ma qualsiasi gestore rimanente in app per /a_route_behind_paywall continuerà ad essere eseguito.
Note
Le chiamate a next() e next(err) indicano che il gestore corrente è completo e in quale stato.
next(err) salterà tutti i gestori rimanenti nella catena ad eccezione di quelli che sono impostati su
gestire gli errori come descritto sopra.