Обработка ошибок в middleware

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

Структура middleware с обработкой ошибок

Middleware в Universal Router представляют собой функции вида:

async function middleware(context, next) {
  // код middleware
  await next();
}

Где:

  • context — объект, содержащий информацию о текущем маршруте, параметрах, состоянии приложения.
  • next — функция, вызывающая следующий middleware или конечный обработчик маршрута.

Для обработки ошибок используется конструкция try/catch:

async function errorHandlingMiddleware(context, next) {
  try {
    await next();
  } catch (err) {
    console.error('Произошла ошибка:', err);
    context.error = err;
    // Можно перенаправить на специальный маршрут ошибки
    return { redirect: '/error' };
  }
}

Ключевые моменты:

  • await next() оборачивается в try/catch, чтобы перехватить любые ошибки в последующих middleware или в конечных маршрутах.
  • Ошибки можно логировать, модифицировать контекст или инициировать редирект.
  • Middleware, который перехватывает ошибку, может возвращать объект с результатом, чтобы контролировать дальнейшее выполнение маршрута.

Принципы последовательной обработки ошибок

  1. Порядок подключения middleware имеет значение. Middleware, обрабатывающий ошибки, должен идти выше по цепочке, чем функции, потенциально вызывающие исключения.
  2. Ошибки “пробрасываются” вверх. Если middleware не перехватывает исключение, оно передается предыдущему уровню, что позволяет централизованно обрабатывать критические сбои.
  3. Контекст как инструмент передачи ошибок. Добавление context.error позволяет другим частям приложения анализировать причины сбоя без необходимости заново выбрасывать исключение.

Асинхронные ошибки и промисы

Все middleware Universal Router поддерживают асинхронные операции. Исключения, возникшие внутри async/await, автоматически превращаются в промис-ошибку, которую можно перехватить:

async function asyncMiddleware(context, next) {
  try {
    const data = await fetchData(context.params.id); // может выбросить ошибку
    context.data = data;
    await next();
  } catch (err) {
    console.warn('Ошибка при загрузке данных:', err.message);
    context.error = err;
    return { component: 'ErrorComponent' };
  }
}

Особое внимание стоит уделять асинхронным функциям внутри middleware, так как необработанная ошибка может остановить весь процесс маршрутизации.

Специальные маршруты ошибок

Universal Router позволяет создавать маршруты для обработки ошибок, чтобы разделить логическую обработку ошибок и отображение ошибок пользователю:

const routes = [
  {
    path: '/error',
    action: () => ({ component: 'ErrorPage' })
  },
  {
    path: '/user/:id',
    action: async ({ params }) => {
      if (!params.id) throw new Error('ID пользователя отсутствует');
      return { component: 'UserPage', userId: params.id };
    }
  }
];

Middleware, перехватывающий ошибки, может перенаправлять на /error или возвращать специализированный компонент для отображения ошибки.

Централизованная обработка ошибок

Для крупных приложений рекомендуется внедрять глобальный middleware, который оборачивает все маршруты:

const router = new UniversalRouter(routes, {
  context: {},
  resolveRoute(context, params) {
    return context.route.action(context, params);
  }
});

router.use(async (context, next) => {
  try {
    await next();
  } catch (err) {
    console.error('Глобальная ошибка:', err);
    context.error = err;
    return { component: 'GlobalErrorComponent' };
  }
});

Такой подход гарантирует, что любая ошибка в маршрутах или других middleware будет обработана единообразно, а приложение не завершится аварийно.

Логирование и повторная генерация ошибок

Помимо перехвата, middleware может выполнять логирование, отправку ошибок на сервер мониторинга или генерацию пользовательских сообщений:

async function loggingMiddleware(context, next) {
  try {
    await next();
  } catch (err) {
    sendToMonitoringService(err); // внешняя система логирования
    throw err; // повторная генерация ошибки для следующего уровня
  }
}

Особенность: повторная генерация ошибки (throw err) позволяет передать управление другому middleware, который может уже корректно отобразить ошибку пользователю.

Практические рекомендации

  • Использовать асинхронные middleware для всех операций ввода/вывода и оборачивать их в try/catch.
  • Разделять middleware на логические слои: проверка данных, авторизация, обработка ошибок, рендеринг.
  • Централизованное логирование ошибок улучшает поддержку и мониторинг приложения.
  • Перехват ошибок на уровне middleware позволяет избежать дублирования кода в каждом маршруте.

Правильная организация middleware и системная обработка ошибок в Universal Router повышают устойчивость приложения и позволяют поддерживать сложные маршруты без потери контроля над исключениями.