Композиция middleware

Page.js предоставляет лёгкую и гибкую систему маршрутизации для одностраничных приложений на JavaScript. Одной из ключевых возможностей является использование middleware — функций, которые выполняются по цепочке при переходе по маршрутам. Middleware позволяют централизованно обрабатывать логику, общую для нескольких маршрутов, например, авторизацию, логирование или подготовку данных.

В Page.js middleware реализуются через функцию page(fn), где fn — функция с сигнатурой (ctx, next). Здесь ctx — объект контекста маршрута, содержащий путь, параметры и состояние, а next — функция, вызывающая следующий middleware в цепочке.


Структура middleware

Middleware в Page.js имеет строгую форму:

function middleware(ctx, next) {
    // Действия до передачи управления следующему middleware
    console.log('Текущий путь:', ctx.path);

    next(); // Передача управления следующему middleware
    // Действия после возврата из следующего middleware (необязательно)
}

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

  • Вызов next() обязателен для продолжения цепочки. Если не вызвать next(), последующие middleware и обработчики маршрутов не выполнятся.
  • Middleware могут быть асинхронными, используя async/await. Это позволяет выполнять запросы к серверу или взаимодействовать с IndexedDB перед продолжением маршрута.

Пример асинхронного middleware:

page(async (ctx, next) => {
    ctx.user = await fetch('/api/current_user').then(res => res.json());
    await next();
});

Композиция middleware

Композиция middleware — это организация нескольких функций в цепочку для одного или нескольких маршрутов. Page.js позволяет назначать middleware глобально или локально.

Глобальные middleware

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

page((ctx, next) => {
    console.log('Каждый маршрут будет проходить через этот middleware');
    next();
});

Локальные middleware

Для конкретного маршрута можно указать несколько функций:

function auth(ctx, next) {
    if (!ctx.user) {
        page('/login');
    } else {
        next();
    }
}

function loadData(ctx, next) {
    ctx.data = fetchDataForRoute(ctx.path);
    next();
}

page('/dashboard', auth, loadData, (ctx) => {
    renderDashboard(ctx.data);
});

Здесь middleware выполняются последовательно, передавая управление с помощью next(). Последняя функция — это финальный обработчик маршрута, который рендерит страницу.


Сочетание глобальных и локальных middleware

Page.js позволяет комбинировать глобальные и локальные middleware, создавая иерархию обработки:

// Глобальный middleware
page((ctx, next) => {
    console.log('Глобальная проверка авторизации');
    next();
});

// Локальные middleware для маршрута
page('/profile', auth, loadProfile, (ctx) => {
    renderProfile(ctx.user);
});

В этом случае порядок выполнения будет следующим:

  1. Глобальный middleware.
  2. Локальные middleware (auth, loadProfile).
  3. Финальный обработчик маршрута.

Такой подход позволяет разделять общие и специфические задачи, избегая дублирования кода.


Управление потоком middleware

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

function requireAdmin(ctx, next) {
    if (!ctx.user || !ctx.user.isAdmin) {
        page('/403');
    } else {
        next();
    }
}

Также можно выполнять действия после выполнения следующего middleware:

page((ctx, next) => {
    console.log('До выполнения следующих middleware');
    next();
    console.log('После выполнения следующих middleware');
});

Это позволяет создавать обёртки и слои обработки, похожие на паттерн middleware в Express.js.


Асинхронная композиция

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

page('/reports', async (ctx, next) => {
    try {
        ctx.reports = await fetch('/api/reports').then(res => res.json());
        await next();
    } catch (err) {
        page('/error');
    }
}, (ctx) => {
    renderReports(ctx.reports);
});

Асинхронные middleware должны всегда вызывать await next(), чтобы гарантировать выполнение следующих функций в цепочке.


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

  1. Общие проверки доступа:
function requireAuth(ctx, next) {
    if (!ctx.user) return page('/login');
    next();
}
  1. Подготовка данных:
function preloadData(resource) {
    return async (ctx, next) => {
        ctx[resource] = await fetch(`/api/${resource}`).then(r => r.json());
        await next();
    };
}

page('/products', requireAuth, preloadData('products'), (ctx) => {
    renderProducts(ctx.products);
});
  1. Логирование и аналитика:
page((ctx, next) => {
    console.log(`[Navigation] Переход на ${ctx.path}`);
    next();
});

Эти паттерны делают middleware мощным инструментом для масштабирования SPA, позволяя писать чистый и модульный код.


Важные рекомендации

  • Middleware должны быть чистыми и модульными — каждая функция выполняет одну задачу.
  • Всегда вызывать next() или перенаправлять пользователя.
  • Использовать асинхронные middleware при работе с сетью или базой данных.
  • Чётко разделять глобальные и локальные middleware, чтобы избежать неожиданного поведения.

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