Gestion des erreurs
Error Handling fait référence à la façon dont Express attrape et traite les erreurs que se produisent de manière synchronisée et asynchrone. Express comes with a default error handler so you don’t need to write your own to get started.
Erreurs de capture
Il est important de s’assurer qu’Express attrape toutes les erreurs qui se produisent lorsque exécute des gestionnaires de routes et des middleware.
Errors in synchronous code
Les erreurs qui se produisent dans le code synchrone dans les gestionnaires de route et les middleware ne nécessitent aucun travail supplémentaire. If synchronous code throws an error, then Express will catch and process it. Par exemple :
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. Par exemple :
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 lance une erreur ou un rejet, next sera appelé avec soit
l’erreur émise ou la valeur rejetée. Si aucune valeur rejetée n’est fournie, next
sera appelée avec un objet Error par défaut fourni par le routeur 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. Par exemple :
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 le callback dans une séquence ne fournit aucune donnée, seulement des erreurs, vous pouvez simplifier ce code comme suit:
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'); },]);Dans l’exemple ci-dessus, next est fourni comme callback pour fs.writeFile,
qui est appelé avec ou sans erreurs. S’il n’y a pas d’erreur, le second
est exécuté, sinon Express attrape et traite l’erreur.
Vous pouvez également utiliser une chaîne de gestionnaires pour vous fier à l’erreur synchrone attrapant, en réduisant le code asynchrone à quelque chose de trivial. Par exemple :
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 cause une erreur, alors il passe l’erreur à Express, sinon vous
revenez rapidement au monde de la gestion des erreurs synchrones dans le prochain gestionnaire
de la chaîne. Ensuite, l’exemple ci-dessus tente de traiter les données. Si cela échoue, alors le gestionnaire d’erreurs synchrone
l’attrapera. Si vous aviez fait ce traitement à l’intérieur de
la callback readFile, alors l’application pourrait quitter et les gestionnaires d’erreur
Express ne s’exécuteraient pas.
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’exemple ci-dessus utilise un bloc try...catch pour attraper des erreurs dans le code
asynchrone et les passer à Express. Si le bloc try...catch
était omis, Express ne attrapera pas l’erreur car il ne fait pas partie du code du gestionnaire
synchrone.
Quelle que soit la méthode que vous utilisez, si vous voulez que les gestionnaires d’erreur Express soient appelés et que l’application survive, vous devez vous assurer qu’Express reçoit l’erreur.
Le gestionnaire d’erreur par défaut
Express est livré avec un gestionnaire d’erreur intégré qui prend en charge toutes les erreurs qui peuvent être rencontrées dans l’application. Cette fonction middleware par défaut est ajoutée à la fin de la pile de fonctions du middleware.
Si vous passez une erreur à next() et que vous ne la gérez pas dans un gestionnaire d’erreur personnalisé
, il sera géré par le gestionnaire d’erreur intégré ; l’erreur sera
écrite au client avec la trace de la pile. La trace de la pile n’est pas incluse
dans l’environnement de production.
Définissez la variable d’environnement NODE_ENV à production, pour exécuter l’application en mode production.
Lorsqu’une erreur est écrite, les informations suivantes sont ajoutées à la réponse :
- Le
res.statusCodeest défini à partir deerr.status(ouerr.statusCode). Si cette valeur est en dehors de la plage 4xx ou 5xx, elle sera définie à 500. - Le
res.statusMessageest défini selon le code de statut. - Le corps sera le HTML du message de code de statut lorsque l’environnement de production
, sinon sera
err.stack. - N’importe quel en-tête spécifié dans un objet
err.headers.
Si vous appelez next() avec une erreur après avoir commencé à écrire la réponse
(par exemple, si vous rencontrez une erreur lors du streaming de la réponse
au client), le gestionnaire d’erreur par défaut Express ferme la connexion
et échoue la requête.
Ainsi, lorsque vous ajoutez un gestionnaire d’erreurs personnalisé, vous devez déléguer à le gestionnaire d’erreur Express par défaut, lorsque les en-têtes ont déjà été envoyés au 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 });}Notez que le gestionnaire d’erreur par défaut peut être déclenché si vous appelez next() avec une erreur
dans votre code plusieurs fois, même si une gestion personnalisée des erreurs est en place.
D’autres erreurs de gestion du middleware peuvent être trouvées dans middleware Express.
Écriture des gestionnaires d’erreurs
Définissez les fonctions du middleware de la même manière que les autres fonctions du middleware,
excepté les fonctions de gestion des erreurs ont quatre arguments au lieu de trois:
(err, req, res, next). Par exemple :
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!');});Vous définissez le middleware en dernier lieu, après d’autres appels app.use() et routage des appels; par exemple:
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});Les réponses provenant d’une fonction middleware peuvent être dans n’importe quel format, comme une page d’erreur HTML, un message simple ou une chaîne JSON.
Pour des raisons organisationnelles (et pour un cadre de plus haut niveau), vous pouvez définir
plusieurs fonctions de gestion des erreurs du middleware, tout comme vous le feriez avec les fonctions
régulières du middleware. Par exemple, pour définir un gestionnaire d’erreurs
pour les requêtes faites en utilisant XHR et ceux sans :
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);Dans cet exemple, les génériques logErrors peuvent écrire la requête et les informations d’erreur
à stderr, par exemple :
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);}Aussi dans cet exemple, clientErrorHandler est défini comme suit ; dans ce cas, l’erreur est explicitement passée au suivant.
Notice that when you do not call next in an error-handling function, you are responsible for writing (and ending) the response. Sinon, ces demandes seront « bloquées » et ne seront pas admissibles au ramassage des déchets.
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); }}Implémenter la fonction “catch-all” errorHandler comme suit (par exemple):
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 vous avez un gestionnaire de route avec plusieurs fonctions de rappel, vous pouvez utiliser le paramètre route pour passer au gestionnaire de route suivant. Par exemple :
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); }); });Dans cet exemple, le gestionnaire getPaidContent sera ignoré, mais tous les gestionnaires restants dans app pour /a_route_behind_paywall continueront d’être exécutés.
Les appels à next() et next(err) indiquent que le gestionnaire actuel est complet et dans quel état.
next(err) sautera tous les gestionnaires restants de la chaîne, à l’exception de ceux qui sont configurés à
gérer les erreurs comme décrit ci-dessus.