Необработанные отказы в экспресс-заявках

У меня много кода, основанного на обещаниях ES6, который работает внутри моего экспресс-приложения. Если есть ошибка, которая никогда не обнаруживается, я использую следующий код для ее устранения:

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

Это отлично подходит для целей отладки.

Однако в процессе производства я хотел бы запустить обработчик ошибок 500, чтобы показать пользователю стандартную страницу «Что-то пошло не так». У меня есть этот обработчик ошибок, который в настоящее время работает для других исключений:

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

Помещение unhandledRejection внутрь промежуточного программного обеспечения не работает, поскольку оно асинхронно, и в результате возникает Error: Can't render headers after they are sent to the client.

Как мне сделать рендеринг страницы 500 на unhandledRejection?


person Daniel    schedule 29.10.2015    source источник


Ответы (6)


Помещение unhandledRejection в промежуточное ПО ... часто приводит к Error: Can't render headers after they are sent to the client.

Внесите небольшие изменения в свой обработчик ошибок:

// 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');
});

Из документации по ExpressJS:

Express поставляется со встроенным обработчиком ошибок, который заботится о любых ошибках, которые могут возникнуть в приложении. Это промежуточное ПО для обработки ошибок по умолчанию добавляется в конец стека промежуточного ПО.

Если вы передаете ошибку в next () и не обрабатываете ее в обработчике ошибок, она будет обработана встроенным обработчиком ошибок - ошибка будет записана клиенту с трассировкой стека. Трассировка стека не включена в производственную среду.

Установите для переменной среды NODE_ENV значение «production», чтобы приложение запускалось в производственном режиме.

Если вы вызываете next () с ошибкой после того, как начали писать ответ, например, если вы столкнулись с ошибкой при потоковой передаче ответа клиенту, обработчик ошибок по умолчанию Express закроет соединение и запрос будет считаться неудачным.

Поэтому, когда вы добавляете настраиваемый обработчик ошибок, вам нужно будет делегировать механизм обработки ошибок по умолчанию в экспресс-режиме, когда заголовки уже отправлены клиенту.

person Jared Dykstra    schedule 04.11.2015
comment
Привет. пожалуйста, добавьте еще код. я не знаю, как обрабатывать unhandledRejection с помощью этого кода process.on('unhandledRejection', error => { /* what should i do here */ }); в экспресс-приложении - person Kasir Barati; 15.09.2020

Я использую аргумент next как обратный вызов catch (также известный как errback), чтобы пересылать любое необработанное отклонение для выражения обработчика ошибок:

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

или более короткая форма:

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

а затем мы могли бы выдать осмысленный ответ об ошибке с аргументом err в экспресс-обработчике ошибок.

Например,

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');
});

ИМХО, глобальное unhandledRejection событие не является окончательным ответом.

например, это подвержено утечке памяти:

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);
  });
});

но это СЛИШКОМ тяжелое:

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);
});

IMHO, expressjs нужна лучшая поддержка Promise.

person iolo    schedule 30.03.2016
comment
поэтому мы всегда должны использовать next для обработки ошибок, и нет глобального обработчика ошибок для отклонения обещаний :( это так плохо! - person Ali Sherafat; 01.07.2017
comment
почему вам нужно удалить прослушиватель событий unhandledRejection, если вы можете добавить его один раз? - person loretoparisi; 20.09.2019
comment
Согласно документам Adds a one-time listener function for the event named eventName. The next time eventName is triggered, this listener is removed and then invoked. - nodejs.org/api/events.html#events_event_removelistener - person loretoparisi; 20.09.2019
comment
@iolo Вы упомянули Too heavy, не могли бы вы объяснить? - person Arjun Singh; 25.02.2020
comment
@loretoparisi Эта ссылка на документ неверна и вводит в заблуждение. - person Neeraj; 27.04.2021

express-promise-router был создан для решения именно этой проблемы. Он позволяет вашим маршрутам возвращать обещания и вызывает next(err), если такое обещание отклоняется с ошибкой.

person Thomas    schedule 15.05.2017
comment
Я думаю, что в настоящее время это действительно правильный путь; о нем гораздо меньше помнить и будет намного проще, чем пытаться разбрасывать логику поиска ошибок по всему вашему коду. - person ZeroG; 11.03.2018

По умолчанию, если ваш запрос - это async функция, которая выдает ошибку, express не передает ошибки промежуточному программному обеспечению. Вместо этого вызывается process.on('unhandledRejection', callback), и запрос блокируется.

Библиотека express-async-errors была создана для устранения этих ошибок.

Вам нужно добавить require('express-async-errors'); в свой код, и библиотека позаботится о том, чтобы все ваши функции поступали в обработчики. Даже если это необработанный отказ.

person David Weinberg    schedule 12.02.2019
comment
Ницца! с этой библиотекой это очень просто. Я бы посоветовал создать свое собственное промежуточное ПО для обработчика ошибок и попытаться поймать все ваши асинхронные вызовы и вызвать next (err) внутри catch, но если вы хотите сэкономить время, эти библиотеки, похоже, работают нормально! - person DarkCrazy; 18.03.2021

Предполагая, что вы используете Express и какой-то код, основанный на обещаниях, например:

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

Добавьте .catch(next) в конец цепочки обещаний, и промежуточное ПО Express будет успешно обрабатывать как синхронный, так и асинхронный код, используя обработчик производственных ошибок.

Вот отличная статья, в которой подробно рассказывается о том, что вы ищете: https://strongloop.com/strongblog/async-error-handling-expressjs-es7-promises-generators/

person Rahat Mahbub    schedule 01.11.2015

Я искал чистый способ справиться с этим, Express 5 теперь обрабатывает асинхронные обещания по дизайну:

Начиная с Express 5, обработчики маршрутов и промежуточное ПО, возвращающие Promise, будут вызывать next (value) автоматически, когда они отклоняют или выдают ошибку. Например

https://expressjs.com/en/guide/error-handling.html

person Sebastien Horin    schedule 14.06.2021