web-dev-qa-db-fra.com

Rejets non gérés dans les applications Express

J'ai beaucoup de code basé sur la promesse ES6 en cours d'exécution dans mon application express. S'il y a une erreur qui n'est jamais interceptée, j'utilise le code suivant pour y faire face:

process.on('unhandledRejection', function(reason, p) {
  console.log("Unhandled Rejection:", reason.stack);
  process.exit(1);
});

Cela fonctionne bien à des fins de débogage.

En production cependant, je voudrais déclencher le gestionnaire d'erreurs 500, pour montrer à l'utilisateur la page standard "Quelque chose s'est mal passé". J'ai ce gestionnaire d'erreurs catch all qui fonctionne actuellement pour d'autres exceptions:

app.use(function(error, req, res, next) {
  res.status(500);
  res.render('500');
});

Mettre le unhandledRejection à l'intérieur d'un middleware ne fonctionne pas car il est asynchrone et offen entraîne un Error: Can't render headers after they are sent to the client.

Comment dois-je procéder pour afficher la page 500 sur un unhandledRejection?

51
Daniel

Mettre le rejet non géré à l'intérieur d'un middleware ... entraîne souvent un Error: Can't render headers after they are sent to the client.

Apportez une légère modification à votre gestionnaire d'erreurs:

// production error handler
const HTTP_SERVER_ERROR = 500;
app.use(function(err, req, res, next) {
  if (res.headersSent) {
    return next(err);
  }

  return res.status(err.status || HTTP_SERVER_ERROR).render('500');
});

Depuis Documentation ExpressJS :

Express est livré avec un gestionnaire d'erreurs intégré, qui prend en charge toutes les erreurs qui pourraient être rencontrées dans l'application. Ce middleware de gestion des erreurs par défaut est ajouté à la fin de la pile des middlewares.

Si vous transmettez une erreur à next () et que vous ne la gérez pas dans un gestionnaire d'erreurs, elle sera gérée par le gestionnaire d'erreurs intégré - l'erreur sera écrite sur le client avec la trace de pile. La trace de pile n'est pas incluse dans l'environnement de production.

Définissez la variable d'environnement NODE_ENV sur "production", pour exécuter l'application en mode production.

Si vous appelez next () avec une erreur après avoir commencé à écrire la réponse, par exemple si vous rencontrez une erreur lors de la diffusion de la réponse vers le client, le gestionnaire d'erreurs par défaut d'Express fermera la connexion et fera échouer la demande.

Ainsi, lorsque vous ajoutez un gestionnaire d'erreurs personnalisé, vous souhaiterez déléguer aux mécanismes de gestion des erreurs par défaut dans express, lorsque les en-têtes ont déjà été envoyés au client.

25
Jared Dykstra

J'utilise l'argument next comme rappel catch (aka errback) pour transmettre tout rejet non géré pour exprimer le gestionnaire d'erreurs:

app.get('/foo', function (req, res, next) {
  somePromise
    .then(function (result) {
      res.send(result);
    })
    .catch(next); // <----- NOTICE!
}

ou forme plus courte:

app.get('/foo', function (req, res, next) {
  somePromise
    .then(function (result) {
       res.send(result); 
    }, next); // <----- NOTICE!
}

et ensuite nous pourrions émettre une réponse d'erreur significative avec l'argument err dans le gestionnaire d'erreur express.

par exemple,

app.use(function (err, req, res, /*unused*/ next) {
  // bookshelf.js model not found error
  if (err.name === 'CustomError' && err.message === 'EmptyResponse') {
    return res.status(404).send('Not Found');
  }
  // ... more error cases...
  return res.status(500).send('Unknown Error');
});

À mon humble avis, l'événement global unhandledRejection n'est pas la réponse ultime.

par exemple, cela est sujet à une fuite de mémoire:

app.use(function (req, res, next) {
  process.on('unhandledRejection', function(reason, p) {
    console.log("Unhandled Rejection:", reason.stack);
    res.status(500).send('Unknown Error');
    //or next(reason);
  });
});

mais c'est AUSSI lourd:

app.use(function (req, res, next) {
  var l = process.once('unhandledRejection', function(reason, p) {
    console.log("Unhandled Rejection:", reason.stack);
    res.status(500).send('Unknown Error');
    //next(reason);
  });
  next();
  process.removeEventLister('unhandledRejection', l);
});

À mon humble avis, expressjs a besoin d'un meilleur support pour Promise.

20
iolo

express-promise-router a été fait pour résoudre exactement ce problème. Il permet à vos itinéraires de renvoyer des promesses et appellera next(err) si une telle promesse est rejetée avec une erreur.

11
Thomas

En supposant que vous utilisez Express et du code basé sur des promesses comme ceci:

readFile() .then(readAnotherFile) .then(doSomethingElse) .then(...)

Ajoutez une .catch(next) à la fin de votre chaîne de promesses et le middleware Express gérera avec succès le code synchrone/asynchrone à l'aide de votre gestionnaire d'erreurs de production.

Voici un excellent article qui explique en détail ce que vous recherchez: https://strongloop.com/strongblog/async-error-handling-expressjs-es7-promises-generators/

3
Rahat Mahbub

Par défaut, si votre requête est une fonction async qui génère une erreur, express ne transmet pas d'erreurs au middleware. Au lieu de cela, process.on('unhandledRejection', callback) est appelée et la requête sera bloquée.

La bibliothèque express-async-errors a été créée pour résoudre ces erreurs.

Vous devez ajouter require('express-async-errors'); à votre code et la bibliothèque s'assurera que toutes vos fonctions arrivent aux gestionnaires. Même s'il s'agit d'un rejet non géré.

2
David Weinberg