Список лучших практик для обработки ошибок в приложении, он может быть применен к любому фреймворку.

Обратные вызовы плохо масштабируются, поскольку большинство программистов с ними не знакомо. Они заставляют проверять ошибки повсюду, разбираются с неприятным вложением кода и затрудняют рассуждение о потоке кода. Библиотеки Promise, такие как BlueBird, async и Q, содержат стандартный стиль кода с использованием RETURN и THROW для управления потоком программы. В частности, они поддерживают любимый стиль обработки ошибок try-catch, который позволяет освободить основной путь кода от ошибок в каждой функции.

Пример кода - использование обещаний для обнаружения ошибок

return functionA()
  .then((valueA) => functionB(valueA))
  .then((valueB) => functionC(valueB))
  .then((valueC) => functionD(valueC))
  .catch((err) => logger.error(err))
  .then(alwaysExecuteThisFunction())

Пример кода - использование async / await для обнаружения ошибок

async function executeAsyncTask () {
  try {
    const valueA = await functionA();
    const valueB = await functionB(valueA);
    const valueC = await functionC(valueB);
    return await functionD(valueC);
  }
  catch(err) {
    logger.error(err);
  }
}

Пример антишаблонного кода

getData(someParameter, function(err, result) {
    if(err !== null) {
        // do something like calling the given callback function and pass the error
        getMoreData(a, function(err, result) {
            if(err !== null) {
                // do something like calling the given callback function and pass the error
                getMoreData(b, function(c) {
                    getMoreData(d, function(e) {
                        if(err !== null ) {
                            // you get the idea?
                        }
                    })
                });
            }
        });
    }
});

Как отличить операционные ошибки от ошибок программиста

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

Пример кода

class AppError {
  constructor (commonType, description, isOperational) {
    Error.call(this);
    Error.captureStackTrace(this);
    this.commonType = commonType;
    this.description = description;
    this.isOperational = isOperational;
  }
};
throw new AppError(errorManagement.commonErrors.InvalidInput, "Describe here what happened", true);

Централизованно обрабатывайте ошибки. Не в промежуточном программном обеспечении

Без одного выделенного объекта для обработки ошибок выше вероятность того, что важные ошибки будут скрыты под радаром из-за неправильной обработки. Объект обработчика ошибок отвечает за то, чтобы сделать ошибку видимой, например, путем записи в хорошо отформатированный регистратор, отправки событий в некоторый продукт мониторинга, такой как Sentry, Rollbar или Raygun. Большинство веб-фреймворков, таких как Express, предоставляют механизм промежуточного программного обеспечения для обработки ошибок. Типичный поток обработки ошибок может быть следующим: какой-то модуль выдает ошибку - ›маршрутизатор API улавливает ошибку -› он передает ошибку промежуточному программному обеспечению (например, Express, KOA), которое отвечает за обнаружение ошибок - ›вызывается централизованный обработчик ошибок - ›Промежуточному программному обеспечению сообщается, является ли эта ошибка ненадежной ошибкой (нерабочей), чтобы оно могло корректно перезапустить приложение. Обратите внимание, что это обычная, но неправильная практика обработки ошибок в промежуточном программном обеспечении Express - это не касается ошибок, возникающих в не веб-интерфейсах.

Пример кода - типичный поток ошибок

// DAL layer, we don't handle errors here
DB.addDocument(newCustomer, (error, result) => {
  if (error)
    throw new Error("Great error explanation comes here", other useful parameters)
});
// API route code, we catch both sync and async errors and forward to the middleware
try {
  customerService.addNew(req.body).then((result) => {
    res.status(200).json(result);
  }).catch((error) => {
    next(error)
  });
}
catch (error) {
  next(error);
}
// Error handling middleware, we delegate the handling to the centralized error handler
app.use(async (err, req, res, next) => {
  const isOperationalError = await errorHandler.handleError(err);
  if (!isOperationalError) {
    next(err);
  }
});

Пример кода - обработка ошибок в выделенном объекте

module.exports.handler = new errorHandler();
function errorHandler() {
  this.handleError = async function(err) {
    await logger.logError(err);
    await sendMailToAdminIfCritical;
    await saveInOpsQueueIfCritical;
    await determineIfOperationalError;
  };
}

Пример кода - Anti Pattern: обработка ошибок в промежуточном программном обеспечении

// middleware handling the error directly, who will handle Cron jobs and testing errors?
app.use((err, req, res, next) => {
  logger.logError(err);
  if (err.severity == errors.high) {
    mailer.sendMail(configuration.adminMail, 'Critical error occured', err);
  }
  if (!err.isOperational) {
    next(err);
  }
});