Глобальные middleware

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


Принцип работы глобальных middleware

Глобальные middleware регистрируются при создании экземпляра роутера или через метод router.use. Каждый middleware получает контекст маршрута и функцию next, которая позволяет передать управление следующему middleware или обработчику маршрута.

Пример базовой структуры глобального middleware:

import UniversalRouter from 'universal-router';

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

// Глобальное middleware
router.use(async (context, next) => {
  console.log('Middleware выполнен перед маршрутом');
  return next();
});

В данном примере console.log выполняется для каждого запроса, а вызов next() гарантирует передачу управления следующему элементу цепочки.


Асинхронная обработка в middleware

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

router.use(async (context, next) => {
  const user = await fetchUserFromAPI(context.userId);
  if (!user) {
    return '/login'; // Перенаправление на другой маршрут
  }
  context.user = user;
  return next();
});

Ключевой момент: middleware могут возвращать новый маршрут или значение. Если возвращается значение, отличное от next(), дальнейшее выполнение цепочки middleware может быть остановлено, а роутер вернет это значение как результат маршрутизации.


Порядок выполнения middleware

Порядок регистрации глобальных middleware важен, так как Universal Router выполняет их строго последовательно:

  1. Первый зарегистрированный middleware выполняется первым.
  2. Каждый следующий middleware вызывается через next().
  3. Если какой-либо middleware возвращает результат без вызова next(), выполнение следующих middleware прекращается.

Пример последовательного выполнения:

router.use(async (context, next) => {
  console.log('Первый middleware');
  await next();
  console.log('Первый middleware после next');
});

router.use(async (context, next) => {
  console.log('Второй middleware');
  await next();
  console.log('Второй middleware после next');
});

Вывод в консоли будет следующим:

Первый middleware
Второй middleware
Второй middleware после next
Первый middleware после next

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


Использование контекста

Контекст (context) является объектом, который передается всем middleware и обработчику маршрута. Глобальные middleware могут изменять контекст для всех маршрутов:

router.use((context, next) => {
  context.startTime = Date.now();
  return next();
});

router.use(async (context, next) => {
  await next();
  console.log(`Маршрут выполнен за ${Date.now() - context.startTime} мс`);
});

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


Перенаправление и обработка ошибок

Глобальные middleware позволяют реализовать глобальные проверки и редиректы. Например, проверка авторизации:

router.use((context, next) => {
  if (!context.user && context.path !== '/login') {
    return '/login';
  }
  return next();
});

Если возвращается строка или объект маршрута, роутер выполнит автоматическое перенаправление. При этом последующие middleware для текущего маршрута могут быть пропущены.

Также можно обрабатывать ошибки централизованно:

router.use(async (context, next) => {
  try {
    return await next();
  } catch (error) {
    console.error('Произошла ошибка маршрутизации:', error);
    return '/error';
  }
});

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


Практические сценарии применения глобальных middleware

  • Аутентификация и авторизация – проверка прав доступа до вызова конкретного маршрута.
  • Логирование и аналитика – запись статистики всех переходов.
  • Глобальная обработка ошибок – единый обработчик ошибок для всех маршрутов.
  • Модификация контекста – добавление общих данных, таких как пользователь, настройки или временные метки.
  • Кеширование и оптимизация – проверка локального кеша перед загрузкой ресурса.

Особенности интеграции с асинхронной логикой

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

router.use(async (context, next) => {
  console.log('Начало глобального middleware');
  await someAsyncCheck();
  const result = await next();
  console.log('Конец глобального middleware');
  return result;
});

Глобальные middleware могут комбинироваться с локальными middleware на уровне конкретного маршрута, создавая гибкую иерархию обработки.


Рекомендации по использованию

  • Глобальные middleware должны быть максимально легковесными, чтобы не замедлять маршрутизацию.
  • Middleware, выполняющие тяжелые операции, лучше делать асинхронными с предварительной проверкой кеша.
  • Четко определять порядок выполнения, особенно если middleware взаимодействуют с контекстом или выполняют редиректы.
  • Использовать middleware для централизованной логики, избегая дублирования кода в каждом маршруте.

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