В клиентской маршрутизации ошибки возникают не реже, чем на сервере: сбои при загрузке данных, неверные параметры URL, ошибки в логике рендеринга. В Page.js отсутствует встроенная сложная система обработки исключений, поэтому управление ошибками ложится на разработчика. Это даёт гибкость, но требует чёткой архитектуры.
Page.js использует цепочку middleware-функций. Каждая функция
принимает аргументы (ctx, next). Ошибка может
возникнуть:
fetch)next()Простейший способ перехвата — использование конструкции
try...catch:
page('/user/:id', async (ctx) => {
try {
const res = await fetch(`/api/users/${ctx.params.id}`);
if (!res.ok) throw new Error('Ошибка загрузки пользователя');
const user = await res.json();
renderUser(user);
} catch (err) {
renderError(err.message);
}
});
Повторение try...catch в каждом маршруте быстро
становится избыточным. Эффективнее вынести обработку в отдельный
middleware.
function errorHandler(ctx, next) {
try {
return next();
} catch (err) {
handleError(err);
}
}
Подключение:
page(errorHandler);
page('/dashboard', (ctx) => {
throw new Error('Ошибка в dashboard');
});
Однако такой подход работает только для синхронных ошибок. Асинхронные требуют иной стратегии.
При использовании async/await ошибки не попадают в
внешний try...catch, если next() возвращает
Promise. Поэтому необходимо явно обрабатывать Promise:
function asyncErrorHandler(ctx, next) {
return Promise.resolve(next()).catch((err) => {
handleError(err);
});
}
Использование:
page(asyncErrorHandler);
page('/data', async () => {
const res = await fetch('/api/data');
if (!res.ok) throw new Error('Ошибка API');
});
Page.js позволяет выстраивать несколько обработчиков:
page('/profile/:id',
loadUser,
checkPermissions,
renderProfile
);
Каждый этап может выбросить ошибку. Важно:
Пример:
async function loadUser(ctx, next) {
const res = await fetch(`/api/users/${ctx.params.id}`);
if (!res.ok) throw new Error('Пользователь не найден');
ctx.user = await res.json();
return next();
}
Если ошибка возникает, выполнение цепочки прерывается.
Для сложных приложений полезно стандартизировать ошибки:
class AppError extends Error {
constructor(message, status = 500) {
super(message);
this.status = status;
}
}
Использование:
if (!user) {
throw new AppError('Пользователь не найден', 404);
}
Централизованный обработчик:
function handleError(err) {
if (err instanceof AppError) {
renderErrorPage(err.status, err.message);
} else {
renderErrorPage(500, 'Внутренняя ошибка');
}
}
Page.js предоставляет fallback через page('*', ...):
page('*', () => {
renderErrorPage(404, 'Страница не найдена');
});
Важно размещать этот маршрут последним, иначе он перехватит все запросы.
Иногда ошибка возникает при переходе, например, из-за некорректного URL:
page('/product/:id', (ctx) => {
const id = Number(ctx.params.id);
if (isNaN(id)) {
throw new Error('Некорректный ID');
}
renderProduct(id);
});
Рекомендуется валидировать параметры маршрута до выполнения основной логики.
Для перехвата непредвиденных ошибок:
window.addEventListener('error', (event) => {
handleError(event.error);
});
window.addEventListener('unhandledrejection', (event) => {
handleError(event.reason);
});
Это особенно важно при работе с Promise.
Ошибки желательно не только отображать, но и логировать:
function handleError(err) {
console.error(err);
sendToMonitoring(err);
renderErrorPage(500, err.message);
}
Где sendToMonitoring может отправлять данные на сервер
или в систему мониторинга.
Пример:
if (!inputValue) {
throw new AppError('Поле обязательно', 400);
}
Системные ошибки не должны раскрывать внутреннюю информацию.
При смешивании синхронных и асинхронных middleware важно соблюдать единый стиль:
page(async (ctx, next) => {
await next();
});
Иначе ошибки могут “теряться” и не доходить до обработчика.
Пример:
function renderErrorPage(status, message) {
document.body.innerHTML = `
<h1>${status}</h1>
<p>${message}</p>
<button oncl ick="location.reload()">Обновить</button>
`;
}
Если используется UI-фреймворк (React, Vue), ошибки могут возникать на уровне компонентов. В таком случае:
Важно не смешивать уровни ответственности.
return next()Такой подход обеспечивает предсказуемое поведение приложения даже при возникновении ошибок.