В современных одностраничных приложениях (SPA) управление
асинхронными запросами становится критически важным. При использовании
Page.js навигация между страницами может инициировать новые
HTTP-запросы или другие асинхронные операции. Если пользователь покидает
страницу до завершения этих операций, оставшиеся запросы могут привести
к неконсистентному состоянию приложения, утечкам памяти или конфликтам
данных.
Page.js не предоставляет встроенного механизма отмены асинхронных операций, поэтому разработчик должен управлять этим самостоятельно. Рассмотрим основные подходы.
AbortController для отмены fetch-запросовДля отмены HTTP-запросов через fetch стандартным
инструментом является AbortController. Его использование
обеспечивает полное управление жизненным циклом запроса.
import page from 'page';
let currentController;
page('/users', async (ctx, next) => {
if (currentController) {
currentController.abort(); // Отмена предыдущего запроса
}
currentController = new AbortController();
const { signal } = currentController;
try {
const response = await fetch('/api/users', { signal });
const data = await response.json();
renderUsers(data);
} catch (err) {
if (err.name === 'AbortError') {
console.log('Запрос был отменен');
} else {
console.error(err);
}
}
});
Ключевые моменты:
AbortController при каждой
навигации.AbortError для корректного
логирования или предотвращения ошибок интерфейса.Если страница инициирует несколько асинхронных операций одновременно, полезно использовать коллекцию контроллеров.
let controllers = [];
page('/dashboard', async () => {
controllers.forEach(c => c.abort());
controllers = [];
const urls = ['/api/stats', '/api/notifications', '/api/messages'];
urls.forEach(url => {
const controller = new AbortController();
controllers.push(controller);
fetch(url, { signal: controller.signal })
.then(res => res.json())
.then(data => renderData(url, data))
.catch(err => {
if (err.name === 'AbortError') {
console.log(`Запрос ${url} отменен`);
} else {
console.error(err);
}
});
});
});
Особенности подхода:
AbortController.abort().Для более системного подхода полезно создавать middleware, которое автоматически управляет отменой запросов.
function cancelPreviousRequests() {
let controller;
return (ctx, next) => {
if (controller) controller.abort();
controller = new AbortController();
ctx.signal = controller.signal;
return next();
};
}
page('/profile', cancelPreviousRequests(), async (ctx) => {
try {
const response = await fetch('/api/profile', { signal: ctx.signal });
const data = await response.json();
renderProfile(data);
} catch (err) {
if (err.name === 'AbortError') {
console.log('Запрос к профилю отменен');
}
}
});
Преимущества:
Не все асинхронные операции используют fetch. Для
таймеров, промисов или других функций можно применять обертки с
контролем отмены.
function cancellableTimeout(ms) {
let timer;
const promise = new Promise((resolve, reject) => {
timer = setTimeout(resolve, ms);
});
promise.cancel = () => clearTimeout(timer);
return promise;
}
let currentTimeout;
page('/loading', async () => {
if (currentTimeout) currentTimeout.cancel();
currentTimeout = cancellableTimeout(5000);
try {
await currentTimeout;
console.log('Завершение операции');
} catch (err) {
console.log('Операция отменена');
}
});
Особенности метода:
AbortController как стандарт для
fetch-запросов.Эффективное управление незавершенными запросами в Page.js обеспечивает стабильность интерфейса, предотвращает утечки памяти и гарантирует, что пользователь всегда видит актуальные данные.