Иногда данные меняются редко, а запросы делать каждый раз не хочется. Простейший вариант - положить результат в localStorage и некоторое время использовать его как кеш.
Идея:
- при первом запросе: загрузить данные по сети и сохранить в localStorage
- при повторном: сначала попробовать взять из localStorage
- при устаревании (TTL): снова сходить в сеть и обновить кеш
1) Как выглядит запись в кеше
Удобно хранить не только данные, но и время сохранения.
const cacheValue = {
savedAt: Date.now(),
data: { items: [1, 2, 3] },
};
Так можно понять, свежий кеш или нет.
2) Мини-функции: get/set для кеша
function cacheSet(key, data) {
const value = {
savedAt: Date.now(),
data,
};
localStorage.setItem(key, JSON.stringify(value));
}
function cacheGet(key) {
const raw = localStorage.getItem(key);
if (!raw) return null;
const parsed = JSON.parse(raw);
if (!parsed || typeof parsed !== "object") return null;
if (!("savedAt" in parsed) || !("data" in parsed)) return null;
return parsed;
}
3) TTL: считать кеш “свежим” только некоторое время
TTL - время жизни кеша в миллисекундах. Например, 10 минут:
const TEN_MINUTES = 10 * 60 * 1000;
Проверка свежести:
function isFresh(savedAt, ttl) {
return Date.now() - savedAt < ttl;
}
4) Полный пример: cache-first с обновлением по сети
async function loadJSON(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error("HTTP " + response.status);
}
return response.json();
}
function cacheSet(key, data) {
const value = { savedAt: Date.now(), data };
localStorage.setItem(key, JSON.stringify(value));
}
function cacheGet(key) {
const raw = localStorage.getItem(key);
if (!raw) return null;
const parsed = JSON.parse(raw);
if (!parsed || typeof parsed !== "object") return null;
if (typeof parsed.savedAt !== "number") return null;
return parsed;
}
function isFresh(savedAt, ttl) {
return Date.now() - savedAt < ttl;
}
async function loadWithCache(url, options) {
const key = options.key;
const ttl = options.ttl;
const cached = cacheGet(key);
if (cached && isFresh(cached.savedAt, ttl)) {
return cached.data;
}
const data = await loadJSON(url);
cacheSet(key, data);
return data;
}
async function run() {
try {
const data = await loadWithCache("/api/data", {
key: "cache:api:data",
ttl: 5 * 60 * 1000,
});
console.log(data);
} catch (e) {
console.log(e.message);
}
}
run();
5) Если localStorage сломан или переполнен
localStorage может быть недоступен (режимы приватности, ограничения) или переполнен. Тогда setItem может выбросить ошибку.
Простой безопасный вариант - оборачивать запись в try/catch:
function cacheSetSafe(key, data) {
try {
const value = { savedAt: Date.now(), data };
localStorage.setItem(key, JSON.stringify(value));
} catch (e) {
return;
}
}
6) Поведение “если сеть упала”: вернуть старый кеш
Иногда лучше показать устаревшие данные, чем ничего. Тогда логика меняется: при ошибке сети - вернуть кеш, даже если он старый.
async function loadWithCacheFallback(url, options) {
const key = options.key;
const ttl = options.ttl;
const cached = cacheGet(key);
if (cached && isFresh(cached.savedAt, ttl)) {
return cached.data;
}
try {
const data = await loadJSON(url);
cacheSet(key, data);
return data;
} catch (e) {
if (cached) {
return cached.data;
}
throw e;
}
}
Что стоит запомнить
- Кеш в localStorage удобен для редко меняющихся данных.
- Хранить лучше вместе с savedAt, чтобы проверять TTL.
- Мини-алгоритм: кеш свежий - вернуть, иначе - сеть - сохранить - вернуть.
- На ошибках сети можно возвращать старый кеш как fallback.
- JSON.parse и localStorage.setItem могут выбрасывать ошибки - это стоит учитывать.