Обработка ошибок через try/catch

localForage построен поверх асинхронных API браузера (IndexedDB, WebSQL, localStorage как fallback), поэтому каждая операция чтения и записи данных потенциально может завершаться ошибкой. В отличие от синхронных API, где ошибка проявляется немедленно через throw, здесь ошибки возвращаются через отклонённые Promise.

Основные категории ошибок:

  • Отсутствие поддержки хранилища: браузер может не поддерживать IndexedDB или WebSQL в текущем контексте (например, приватный режим с ограничениями).
  • Превышение квоты: попытка записать данные сверх лимита доступного хранилища.
  • Повреждение базы IndexedDB: редкие, но критичные случаи, когда база становится недоступной.
  • Ошибки сериализации: невозможность корректно сохранить сложные структуры данных.
  • Контекст выполнения: ошибки в SSR (server-side rendering), Web Worker или sandboxed iframe.
  • Проблемы доступа: блокировка со стороны браузера или политики безопасности.

Каждая из этих ситуаций должна обрабатываться единообразно через механизмы try/catch в сочетании с async/await или через .catch() у Promise.

Базовая модель обработки ошибок через async/await

Основной подход при работе с 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;
        }
    }
}

Такой подход используется в кэширующих системах, где допустимо удаление устаревших данных.

Использование then/catch и централизованная обработка

Несмотря на распространённость 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 использует сериализацию для хранения сложных объектов. Однако не все структуры могут быть корректно сериализованы.

Проблемные случаи:

  • циклические ссылки
  • функции внутри объектов
  • BigInt в некоторых окружениях
  • нестандартные классы без поддержки структурного клонирования
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 может вести себя по-разному в зависимости от окружения:

SSR (Server-Side Rendering)

На сервере отсутствует 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;
    }
}

Web Worker

В некоторых случаях IndexedDB доступен, но поведение может отличаться, и ошибки доступа становятся более вероятными.

Ограниченные iframe

Политики безопасности (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);
        }
    }
}

Такой слой позволяет:

  • унифицировать обработку ошибок
  • централизовать логирование
  • внедрять retry-логику
  • управлять fallback-стратегиями

Retry-стратегии при временных сбоях

Некоторые ошибки являются временными (например, блокировка 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 важно фиксировать:

  • тип операции (get/set/remove)
  • ключ
  • размер данных
  • тип ошибки
  • браузерное окружение
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 и бизнес-логике.