Ответ
Под "восстановлением ресурсов" в Node.js я понимаю стратегии для эффективной и отказоустойчивой загрузки данных клиентом, особенно после сбоев или для больших объёмов данных. Вот как я это реализовывал:
1. Пагинация с курсорами для стабильности: Вместо offset/limit пагинации, которая может "плыть" при добавлении новых данных, использовал курсорную пагинацию на основе времени создания или уникального ID.
// API Endpoint в Express
app.get('/api/transactions', async (req, res) => {
const limit = parseInt(req.query.limit) || 50;
const cursor = req.query.cursor; // ID последней полученной записи
let query = {};
if (cursor) {
// Находим запись по курсору и выбираем следующие
const lastItem = await Transaction.findById(cursor);
if (lastItem) {
query.createdAt = { $lt: lastItem.createdAt };
}
}
const transactions = await Transaction.find(query)
.sort({ createdAt: -1 })
.limit(limit + 1); // Берём на одну больше, чтобы проверить, есть ли ещё данные
const hasNextPage = transactions.length > limit;
const items = hasNextPage ? transactions.slice(0, -1) : transactions;
const nextCursor = hasNextPage ? items[items.length - 1]._id : null;
res.json({
items,
pageInfo: {
hasNextPage,
nextCursor
}
});
});
2. Механизм повторных попыток (Retry Logic) на клиенте: При временном сбое сети или ошибке 5xx сервера, фронтенд автоматически повторяет запрос.
// Утилитарная функция для запросов с повторными попытками
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url, options);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
} catch (error) {
if (i === maxRetries - 1) throw error; // Последняя попытка
// Экспоненциальная задержка перед повторной попыткой
const delay = Math.pow(2, i) * 1000 + Math.random() * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
console.warn(`Попытка ${i + 1} не удалась, повтор через ${delay}мс`);
}
}
}
// Использование
const data = await fetchWithRetry('/api/large-dataset');
3. Чанковая загрузка (Chunked Upload/Download) для больших файлов:
Для восстановления прерванной загрузки файла использовал библиотеку tus-js-client на фронтенде и tus-node-server на бэкенде, которая поддерживает возобновляемую загрузку.
4. Кеширование ответов API в Service Worker: Реализовывал стратегию "Cache then Network" для критичных данных, чтобы при отсутствии сети пользователь видел закешированную, пусть и устаревшую, версию.
5. Сохранение состояния на клиенте: Использовал Zustand или Context API в React для хранения уже загруженных данных. При повторном открытии приложения или вкладки, сначала отображались данные из кеша состояния, а затем в фоне происходил запрос на актуализацию. Это создавало ощущение мгновенной загрузки и позволяло работать оффлайн.
Ответ 18+ 🔞
Э, слушай, история-то простая, но охуенно важная. Когда твой клиент пытается вытянуть дохуя данных, а сеть подводит — это пиздец как бесит. Вот как я обычно делаю, чтобы не было потом истерики у пользователей.
1. Пагинация, которая не ебётся при добавлении новых данных. Классический offset/limit — это мартышлюшка, чувак. Представь: ты на пятой странице, а пока листал, сверху добавили новые записи. Всё, ты пропустил кучу данных или увидишь дубли. Полный пиздец. Поэтому я юзаю курсоры, обычно по времени или ID.
// API Endpoint в Express
app.get('/api/transactions', async (req, res) => {
const limit = parseInt(req.query.limit) || 50;
const cursor = req.query.cursor; // ID последней полученной записи
let query = {};
if (cursor) {
// Находим запись по курсору и выбираем следующие
const lastItem = await Transaction.findById(cursor);
if (lastItem) {
query.createdAt = { $lt: lastItem.createdAt };
}
}
const transactions = await Transaction.find(query)
.sort({ createdAt: -1 })
.limit(limit + 1); // Берём на одну больше, чтобы проверить, есть ли ещё данные
const hasNextPage = transactions.length > limit;
const items = hasNextPage ? transactions.slice(0, -1) : transactions;
const nextCursor = hasNextPage ? items[items.length - 1]._id : null;
res.json({
items,
pageInfo: {
hasNextPage,
nextCursor
}
});
});
Вот так всё стабильно, как швейцарские часы. Никаких внезапных сюрпризов.
2. Механизм повторных попыток — без этого нихуя. Сеть — она, блядь, такая хитрая жопа. Сегодня есть, завтра — хуй с горы. Поэтому на клиенте надо делать запросы с повторными попытками. А то пользователь ткнет раз, ошибка 500, и он уже в истерике, думает, что всё накрылось медным тазом.
// Утилитарная функция для запросов с повторными попытками
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url, options);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
} catch (error) {
if (i === maxRetries - 1) throw error; // Последняя попытка
// Экспоненциальная задержка перед повторной попыткой
const delay = Math.pow(2, i) * 1000 + Math.random() * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
console.warn(`Попытка ${i + 1} не удалась, повтор через ${delay}мс`);
}
}
}
// Использование
const data = await fetchWithRetry('/api/large-dataset');
Смотри, логика простая: первая ошибка — ждём секунду, вторая — две, третья — четыре. Чаще всего хватает. Удивление пиздец, но это реально работает.
3. Для больших файлов — чанковая загрузка.
Пытаться залить гигабайтный файл одним запросом — это, ёпта, чистой воды самоубийство. Обрыв на 99% — и привет, начинай сначала, терпения ноль ебать. Я использовал tus-js-client на фронте и tus-node-server на бэке. Эта штука умеет возобновлять загрузку с места обрыва. Просто космос, а не технология.
4. Кеширование в Service Worker. Чтобы даже при полном отсутствии сети пользователь не смотрел в пустой экран, как дурак. Реализуешь стратегию "Cache then Network" — сначала показываешь старые, закешированные данные (пусть даже вчерашние), а потом в фоне тихонько подтягиваешь свежие. Волнение ебать у пользователя сразу спадает.
5. Сохранение состояния на клиенте. Zustand, Context API — неважно. Суть в том, чтобы при повторном открытии приложения у тебя сразу отображалось то, что ты уже видел. Создаётся ощущение, что всё грузится мгновенно. Пока ты моргнул, данные уже на экране, а на сервере только запрос ушёл на актуализацию. Пользователь доволен, а доверия ебать ноль превращается в "ого, как быстро".
Вот так, без лишней ебалы и нервотрёпки, можно сделать загрузку данных отказоустойчивой и не выносить мозг ни себе, ни людям.