Композиция обработчиков

Navigo — это лёгкий роутер для JavaScript, ориентированный на работу с одностраничными приложениями (SPA). Одной из ключевых особенностей является возможность композиции обработчиков, позволяющей структурировать логику маршрутов, объединять повторяющиеся действия и управлять сложными сценариями навигации.

Композиция обработчиков позволяет разделять ответственность между функциями и создавать цепочки действий, которые выполняются при совпадении маршрута. В основе этого лежит функциональный подход: каждый обработчик является функцией, принимающей объект параметров маршрута, контекст или состояние приложения.


Создание простого обработчика

Базовый обработчик в Navigo выглядит следующим образом:

const router = new Navigo('/');

function showHome() {
  console.log('Главная страница');
}

router.on('/', showHome);

В этом случае обработчик showHome выполняется при переходе на /. Для композиции важно понимать, что обработчик — это обычная функция, которую можно оборачивать, комбинировать или цеплять с другими функциями.


Использование промежуточных функций (middleware)

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

Пример реализации промежуточной функции:

function authMiddleware(params, next) {
  if (user.isAuthenticated) {
    next(); // передаём управление следующей функции
  } else {
    router.navigate('/login');
  }
}

function logMiddleware(params, next) {
  console.log('Навигация на маршрут:', params);
  next();
}

function showDashboard() {
  console.log('Панель управления');
}

router.on('/dashboard', (params, query) => {
  authMiddleware(params, () => {
    logMiddleware(params, showDashboard);
  });
});

В этом примере каждый middleware получает управление и может передать его следующей функции через next. Такой подход позволяет создавать цепочки функций без дублирования кода.


Цепочка обработчиков через композицию функций

Более удобный способ — создание утилиты для объединения нескольких функций в одну цепочку:

function composeHandlers(...handlers) {
  return function(params) {
    let index = -1;
    function next() {
      index++;
      if (index < handlers.length) {
        handlers[index](params, next);
      }
    }
    next();
  }
}

function middlewareA(params, next) {
  console.log('Middleware A');
  next();
}

function middlewareB(params, next) {
  console.log('Middleware B');
  next();
}

function finalHandler(params) {
  console.log('Основной обработчик', params);
}

router.on('/composed', composeHandlers(middlewareA, middlewareB, finalHandler));

В этом коде composeHandlers позволяет объединять несколько функций в один единый обработчик, сохраняя порядок выполнения и контроль передачи управления.


Асинхронные обработчики

Navigo поддерживает асинхронные функции. При композиции асинхронных обработчиков важно использовать async/await или промисы, чтобы правильно ожидать завершения каждой функции перед переходом к следующей:

async function fetchData(params, next) {
  const data = await fetch(`/api/data/${params.id}`).then(res => res.json());
  params.data = data;
  next();
}

async function showData(params) {
  console.log('Данные:', params.data);
}

router.on('/data/:id', composeHandlers(fetchData, showData));

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


Применение композиции в сложных приложениях

  1. Разделение ответственности: каждый middleware выполняет строго определённую задачу — проверка авторизации, логирование, загрузка данных.
  2. Повторное использование: одни и те же функции можно подключать к разным маршрутам без дублирования кода.
  3. Управление ошибками: можно вставлять обработчики ошибок в цепочку, чтобы перехватывать исключения на любом этапе.
  4. Управление асинхронностью: композиция позволяет последовательно выполнять асинхронные операции, сохраняя читаемость кода.

Рекомендации по организации цепочек

  • Минимизировать количество уровней вложенности: чем меньше уровней next, тем легче отлаживать цепочку.
  • Явно передавать параметры: все промежуточные функции должны использовать объект params и, при необходимости, модифицировать его.
  • Сохранять однообразие: все обработчики должны иметь одинаковый интерфейс (params, next) для совместимости с утилитой композиции.
  • Разделять асинхронные и синхронные обработчики: смешивание может привести к непредсказуемому поведению.

Итоговая структура обработчиков

В больших приложениях рекомендуется строить модульные цепочки, где:

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

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