localForage построен поверх асинхронных API браузера (IndexedDB,
WebSQL, localStorage как fallback), поэтому каждая операция чтения и
записи данных потенциально может завершаться ошибкой. В отличие от
синхронных API, где ошибка проявляется немедленно через
throw, здесь ошибки возвращаются через отклонённые
Promise.
Основные категории ошибок:
Каждая из этих ситуаций должна обрабатываться единообразно через
механизмы try/catch в сочетании с async/await
или через .catch() у Promise.
Основной подход при работе с localForage заключается в использовании
async/await, где try/catch становится
центральным механизмом контроля ошибок.
import localforage from "localforage";
async function saveUserData(key, value) {
try {
await localforage.setItem(key, value);
} catch (error) {
console.error("Ошибка при сохранении данных:", error);
}
}
В данном примере любая ошибка, возникающая в процессе записи, будет
перехвачена блоком catch. Это может быть как ошибка квоты,
так и внутренняя ошибка IndexedDB.
Важно, что try/catch охватывает именно
await-операцию. Если Promise отклоняется, управление
немедленно передаётся в catch.
Чтение данных также является асинхронной операцией, и ошибка может
возникнуть даже при простом getItem, например, при
повреждённой базе или отсутствии доступа.
async function loadUserData(key) {
try {
const value = await localforage.getItem(key);
return value;
} catch (error) {
console.error("Ошибка при чтении данных:", error);
return null;
}
}
Возврат null в случае ошибки является распространённой
стратегией, позволяющей отделить отсутствие данных от критического сбоя.
Однако в более строгих системах может использоваться повторное
выбрасывание ошибки:
async function loadUserDataStrict(key) {
try {
return await localforage.getItem(key);
} catch (error) {
throw new Error("Не удалось загрузить данные из localForage");
}
}
Ключевой момент при обработке ошибок localForage — различие между:
null как валидным результатом отсутствия значенияconst value = await localforage.getItem("token");
Если ключ не существует, результатом будет null, и это
не ошибка. Ошибка возникает только при сбоях хранилища
или окружения.
Корректная обработка должна учитывать это различие:
async function getToken() {
try {
const token = await localforage.getItem("token");
if (token === null) {
console.warn("Токен отсутствует");
return null;
}
return token;
} catch (error) {
console.error("Ошибка доступа к хранилищу:", error);
return null;
}
}
Наиболее частая проблема при работе с localForage — превышение лимита хранилища. IndexedDB имеет ограничение, зависящее от браузера и устройства.
async function cacheLargeData(key, data) {
try {
await localforage.setItem(key, data);
} catch (error) {
if (error && error.name === "QuotaExceededError") {
console.error("Превышена квота хранилища");
} else {
console.error("Неизвестная ошибка записи:", error);
}
}
}
Типизация ошибки важна, поскольку позволяет различать стратегию обработки:
Пример стратегии очистки:
async function safeSetItem(key, value) {
try {
await localforage.setItem(key, value);
} catch (error) {
if (error.name === "QuotaExceededError") {
await localforage.clear();
await localforage.setItem(key, value);
} else {
throw error;
}
}
}
Такой подход используется в кэширующих системах, где допустимо удаление устаревших данных.
Несмотря на распространённость async/await,
Promise-цепочки остаются актуальными, особенно при функциональной
композиции.
localforage.getItem("settings")
.then((value) => {
console.log("Настройки:", value);
})
.catch((error) => {
console.error("Ошибка загрузки настроек:", error);
});
Особенность такого подхода — локальная обработка ошибок на уровне
цепочки, без необходимости try/catch.
Однако при сложной логике цепочки ошибок могут быть потеряны, если
отсутствует финальный catch:
localforage.setItem("a", 1)
.then(() => localforage.setItem("b", 2))
.then(() => localforage.setItem("c", 3))
.catch((error) => {
console.error("Ошибка в цепочке операций:", error);
});
Любая ошибка в цепочке автоматически прерывает выполнение и передаёт
управление в catch.
localForage использует сериализацию для хранения сложных объектов. Однако не все структуры могут быть корректно сериализованы.
Проблемные случаи:
const circular = {};
circular.self = circular;
try {
await localforage.setItem("bad", circular);
} catch (error) {
console.error("Ошибка сериализации:", error);
}
Такие ошибки часто имеют тип DataCloneError.
Для предотвращения подобных ситуаций используется предварительная нормализация данных:
function sanitizeData(obj) {
return JSON.parse(JSON.stringify(obj));
}
Хотя этот метод ограничен, он позволяет отфильтровать неподдерживаемые структуры перед сохранением.
localForage может вести себя по-разному в зависимости от окружения:
На сервере отсутствует IndexedDB, поэтому любые вызовы могут приводить к ошибкам.
async function safeGetItem(key) {
try {
if (typeof window === "undefined") {
return null;
}
return await localforage.getItem(key);
} catch (error) {
console.error("Ошибка в SSR-контексте:", error);
return null;
}
}
В некоторых случаях IndexedDB доступен, но поведение может отличаться, и ошибки доступа становятся более вероятными.
Политики безопасности (sandbox) могут блокировать доступ к storage API, что приводит к немедленным отклонениям Promise.
В крупных приложениях прямое использование try/catch в
каждом вызове localForage приводит к дублированию логики. Поэтому
формируется слой-обёртка.
class StorageService {
constructor(store) {
this.store = store;
}
async get(key) {
try {
return await this.store.getItem(key);
} catch (error) {
console.error("Storage GET error:", error);
return null;
}
}
async set(key, value) {
try {
return await this.store.setItem(key, value);
} catch (error) {
console.error("Storage SET error:", error);
throw error;
}
}
async remove(key) {
try {
await this.store.removeItem(key);
} catch (error) {
console.error("Storage REMOVE error:", error);
}
}
}
Такой слой позволяет:
Некоторые ошибки являются временными (например, блокировка IndexedDB во время миграции). В таких случаях применяется повторная попытка.
async function retrySetItem(key, value, attempts = 3) {
for (let i = 0; i < attempts; i++) {
try {
await localforage.setItem(key, value);
return;
} catch (error) {
if (i === attempts - 1) {
throw error;
}
}
}
}
Такая стратегия повышает устойчивость системы без усложнения бизнес-логики.
Эффективная обработка ошибок невозможна без структурированного логирования. В контексте localForage важно фиксировать:
function logStorageError(operation, key, error) {
console.group("localForage error");
console.log("Operation:", operation);
console.log("Key:", key);
console.log("Error:", error.name, error.message);
console.groupEnd();
}
Такой подход упрощает диагностику проблем, связанных с IndexedDB и ограничениями браузеров.
Даже при строгой типизации ошибок возможны неожиданные исключения. Поэтому финальный уровень защиты всегда предполагает универсальный обработчик:
async function safeOperation(fn) {
try {
return await fn();
} catch (error) {
console.error("Critical storage error:", error);
return null;
}
}
Использование подобной обёртки позволяет изолировать сбои localForage от остальной части приложения и предотвращает каскадные ошибки в UI и бизнес-логике.