idb-keyval — это легковесная библиотека для работы с IndexedDB через простой интерфейс ключ-значение. Основная задача при работе с кэшем — гарантировать, что данные, получаемые из хранилища, актуальны. Для этого используются стратегии инвалидации кэша, которые позволяют автоматически или вручную удалять устаревшие записи.
Базовые методы библиотеки:
import { get, set, del, clear } from 'idb-keyval';
// Сохранение данных
await set('userProfile', { name: 'Alice', age: 30 });
// Получение данных
const profile = await get('userProfile');
// Удаление данных
await del('userProfile');
// Очистка всего хранилища
await clear();
Эти методы являются фундаментом для реализации любой стратегии кэширования. Для контроля актуальности данных важно внедрять метаданные, такие как время создания или время жизни (TTL — Time To Live).
Для реализации автоматической инвалидации кэша необходимо хранить не только данные, но и время их записи:
const setWithTTL = async (key, value, ttl) => {
const record = {
value,
expiry: Date.now() + ttl
};
await set(key, record);
};
const getWithTTL = async (key) => {
const record = await get(key);
if (!record) return null;
if (Date.now() > record.expiry) {
await del(key);
return null;
}
return record.value;
};
Ключевые моменты:
expiry хранит точку во времени, после которой данные
считаются устаревшими.1. FIFO (First In, First Out) Подходит для кэшей с ограниченной емкостью. Старые записи удаляются первыми, чтобы освободить место для новых. Реализуется через хранение очереди ключей:
const cacheQueue = [];
const setFIFO = async (key, value, maxSize = 50) => {
await set(key, value);
cacheQueue.push(key);
if (cacheQueue.length > maxSize) {
const oldestKey = cacheQueue.shift();
await del(oldestKey);
}
};
2. LRU (Least Recently Used) Удаляются данные, которые дольше всего не использовались. Для этого нужно вести учет времени последнего доступа:
const setLRU = async (key, value) => {
const record = { value, lastAccessed: Date.now() };
await set(key, record);
};
const getLRU = async (key) => {
const record = await get(key);
if (!record) return null;
record.lastAccessed = Date.now();
await set(key, record);
return record.value;
};
3. Инвалидация по событиям Данные кэша можно сбрасывать при возникновении определенных событий, например, обновлении данных на сервере:
const invalidateOnUpdate = async (keys) => {
for (const key of keys) {
await del(key);
}
};
В реальных приложениях часто используют комбинацию TTL и LRU:
Пример комбинированного подхода:
const setCache = async (key, value, ttl, cacheSize = 50) => {
await setLRU(key, { value, expiry: Date.now() + ttl });
if (cacheQueue.length > cacheSize) {
const lruKey = cacheQueue.shift(); // удаляем наименее используемый
await del(lruKey);
}
};
const getCache = async (key) => {
const record = await getLRU(key);
if (!record || Date.now() > record.expiry) {
await del(key);
return null;
}
return record.value;
};
Работа с IndexedDB через idb-keyval может быть прервана или завершена
ошибкой. Рекомендуется оборачивать операции в блоки
try-catch:
try {
await set('config', { theme: 'dark' });
} catch (err) {
console.error('Ошибка сохранения данных в IndexedDB:', err);
}
Также полезно иметь механизм отката, когда устаревшая запись не может быть удалена:
const safeDel = async (key) => {
try {
await del(key);
} catch (err) {
console.warn(`Не удалось удалить ключ ${key}:`, err);
}
};
Для оптимизации стратегии инвалидации важно вести статистику использования:
Простейший пример счетчиков:
const cacheStats = { hits: 0, misses: 0 };
const getWithStats = async (key) => {
const value = await get(key);
if (value) cacheStats.hits++;
else cacheStats.misses++;
return value;
};
Это позволяет адаптировать стратегию под реальные условия работы приложения и минимизировать задержки при доступе к данным.
Инвалидация кэша с использованием idb-keyval — это не просто удаление старых данных. Грамотное внедрение TTL, LRU и событийной инвалидации обеспечивает:
Сочетание этих подходов позволяет создавать гибкие и надежные клиентские кэши, адаптированные под конкретные сценарии использования.