Отмена перехода

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

Отмена перехода позволяет:

  • предотвращать навигацию при несоблюдении условий;
  • реализовывать защиту от потери данных;
  • управлять доступом к страницам;
  • внедрять асинхронные проверки (например, авторизация).

Основы работы маршрутов в Page.js

Каждый маршрут в Page.js представляет собой цепочку middleware-функций. Эти функции выполняются последовательно при переходе по URL.

Пример:

page('/profile', checkAuth, loadProfile, renderProfile);

Каждая функция получает параметры:

function middleware(ctx, next) {
  // логика
  next();
}
  • ctx — объект контекста (содержит путь, параметры, состояние);
  • next — функция для передачи управления следующему middleware.

Ключевая идея: если next() не вызывается, переход останавливается.


Простейшая отмена перехода

Самый базовый способ отмены перехода — не вызывать next():

function blockRoute(ctx, next) {
  console.log('Переход запрещён');
}
page('/admin', blockRoute, renderAdmin);

В этом случае:

  • renderAdmin никогда не выполнится;
  • URL уже изменился (если переход инициирован вручную);
  • логика цепочки остановлена.

Перенаправление вместо отмены

Часто вместо полной остановки требуется перенаправление:

function requireAuth(ctx, next) {
  if (!user.isAuthenticated) {
    page('/login');
    return;
  }
  next();
}

Особенности:

  • текущий переход прерывается;
  • инициируется новый маршрут;
  • стек middleware перезапускается.

Использование page.redirect

Библиотека предоставляет встроенный способ редиректа:

page('/old-route', () => {
  page.redirect('/new-route');
});

или:

page.redirect('/old-route', '/new-route');

Это декларативный способ отмены перехода с заменой маршрута.


Асинхронная отмена перехода

Page.js не имеет встроенной поддержки async/await, но позволяет реализовать асинхронную логику вручную:

function checkPermission(ctx, next) {
  fetch('/api/permission')
    .then(res => res.json())
    .then(data => {
      if (data.allowed) {
        next();
      } else {
        page('/forbidden');
      }
    });
}

Важно:

  • next() вызывается только после завершения асинхронной операции;
  • при отсутствии вызова переход считается отменённым.

Отмена перехода при наличии несохранённых данных

Распространённый сценарий — предупреждение пользователя:

function preventDataLoss(ctx, next) {
  if (form.isDirty) {
    const confirmLeave = window.confirm('Есть несохранённые изменения. Продолжить?');
    if (!confirmLeave) return;
  }
  next();
}

Особенности:

  • блокировка происходит до выполнения следующего middleware;
  • используется синхронный диалог браузера.

Глобальные middleware и отмена переходов

Page.js позволяет регистрировать middleware для всех маршрутов:

page('*', globalGuard);
function globalGuard(ctx, next) {
  if (!app.isReady) {
    return;
  }
  next();
}

Применение:

  • централизованный контроль доступа;
  • проверка состояния приложения;
  • логирование переходов.

Обработка ошибок как форма отмены

Отмена перехода может сопровождаться обработкой ошибок:

function validateRoute(ctx, next) {
  if (!ctx.params.id) {
    throw new Error('Invalid route');
  }
  next();
}

Page.js поддерживает обработчик ошибок:

page('/user/:id', validateRoute, renderUser);

page('*', (ctx) => {
  console.error('Ошибка маршрута');
});

Использование состояния (ctx.state) для контроля перехода

Объект ctx может хранить пользовательские данные:

function checkState(ctx, next) {
  if (ctx.state.blocked) {
    return;
  }
  next();
}

Это позволяет:

  • управлять переходами на основе динамических условий;
  • передавать флаги между middleware.

Отмена перехода при программной навигации

Если переход инициирован через page():

page('/settings');

и в middleware не вызывается next(), происходит:

  • изменение URL;
  • остановка выполнения цепочки;
  • отсутствие рендера.

Для предотвращения изменения URL требуется контролировать момент вызова page().


Паттерн “охранника маршрута” (Route Guard)

Распространённая архитектура:

function guard(ctx, next) {
  if (!user) {
    page('/login');
    return;
  }
  next();
}

page('/dashboard', guard, renderDashboard);

Преимущества:

  • повторное использование логики;
  • централизованная защита маршрутов;
  • упрощение масштабирования.

Комбинирование нескольких проверок

page('/secure',
  checkAuth,
  checkSubscription,
  checkPermissions,
  renderSecurePage
);

Любая функция может остановить переход:

function checkSubscription(ctx, next) {
  if (!user.hasSubscription) {
    page('/subscribe');
    return;
  }
  next();
}

Побочные эффекты при отмене перехода

При остановке маршрута важно учитывать:

  • DOM может остаться в предыдущем состоянии;
  • частично выполненные middleware не откатываются;
  • асинхронные операции могут завершиться позже.

Рекомендуется:

  • избегать побочных эффектов до вызова next();
  • использовать чистые функции в middleware.

Отладка отменённых переходов

Полезные техники:

function debug(ctx, next) {
  console.log('Переход к:', ctx.path);
  next();
}

Логирование перед потенциальной отменой:

function guard(ctx, next) {
  console.log('Проверка доступа');

  if (!user) {
    console.warn('Доступ запрещён');
    return;
  }

  next();
}

Расширенные сценарии

Кэширование и отмена

function cacheGuard(ctx, next) {
  if (cache.has(ctx.path)) {
    render(cache.get(ctx.path));
    return;
  }
  next();
}

Ленивая загрузка

function lazyLoad(ctx, next) {
  import('./page.js').then(module => {
    module.render();
  });
}

Здесь next() может не использоваться вовсе, полностью заменяя цепочку.


Взаимодействие с history API

Page.js работает поверх History API браузера. При отмене перехода:

  • запись в истории уже может быть создана;
  • возврат назад может привести к повторному вызову маршрута.

Для контроля:

window.addEventListener('popstate', (e) => {
  console.log('Навигация назад/вперёд');
});

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

  • Останавливать переход как можно раньше в цепочке middleware
  • Избегать сложной логики после next()
  • Централизовать проверки через глобальные middleware
  • Использовать редирект вместо “глухой” отмены
  • Минимизировать побочные эффекты до завершения проверок

Типичные ошибки

1. Забытый return после редиректа

if (!user) {
  page('/login');
}
next(); // ошибка

Правильно:

if (!user) {
  page('/login');
  return;
}
next();

2. Асинхронный код без контроля

fetch('/api').then(() => next());
next(); // дублирование

3. Смешивание логики рендера и проверки

Лучше разделять:

page('/route', check, render);

Поведение при вложенных маршрутах

Page.js не имеет встроенной системы вложенных маршрутов, но можно имитировать:

page('/admin/*', adminGuard);
page('/admin/users', renderUsers);

Если adminGuard не вызывает next(), все вложенные маршруты блокируются.


Роль next() в управлении переходом

next() — ключевой механизм:

  • запускает следующий middleware;
  • определяет, будет ли завершён переход;
  • позволяет строить цепочки обработки.

Отсутствие вызова = фактическая отмена перехода.


Итоговая модель

Процесс перехода в Page.js:

  1. Инициируется навигация

  2. Формируется ctx

  3. Запускается цепочка middleware

  4. Каждый шаг решает:

    • продолжить (next())
    • остановить (ничего не делать)
    • перенаправить (page())

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