Инициализация клиентского хранилища через localForage при старте приложения определяет стабильность работы всей слоя персистентности: от загрузки пользовательских настроек до восстановления кэша данных, синхронизационных очередей и офлайн-состояния. Ошибки на этом этапе часто проявляются не сразу, а в виде «плавающих» дефектов, связанных с недоступностью IndexedDB, падением на Safari Private Mode или некорректным выбором драйвера.
Базовая проблема заключается в том, что localForage работает поверх нескольких backend-хранилищ (IndexedDB, WebSQL, localStorage), и выбор конкретного драйвера происходит асинхронно. Это означает, что обращение к хранилищу до завершения инициализации может привести к неконсистентному состоянию.
На практике выделяются три стратегии:
Наиболее распространённый подход — настройка глобального экземпляра до монтирования приложения.
import localForage from "localforage";
localForage.config({
name: "myApp",
storeName: "app_storage",
version: 1.0,
description: "Main persistent storage"
});
Эта конфигурация должна выполняться максимально рано: до подключения сервисов, стора и UI-слоя. Важно понимать, что конфигурация не блокирует выполнение кода — она лишь задаёт параметры будущего подключения к backend.
Для предсказуемости поведения часто фиксируют порядок предпочтений драйверов:
import localForage from "localforage";
localForage.setDriver([
localForage.INDEXEDDB,
localForage.WEBSQL,
localForage.LOCALSTORAGE
]);
Этот шаг критичен в приложениях, где IndexedDB может быть нестабильным или заблокированным политиками браузера. Установка драйвера — асинхронная операция, требующая ожидания завершения.
Ключевой момент инициализации — подтверждение того, что backend выбран и готов к операциям.
import localForage from "localforage";
async function initStorage() {
await localForage.ready();
return true;
}
Метод ready() гарантирует, что:
Игнорирование этого шага приводит к состояниям гонки между UI и storage-слоем.
В архитектурно зрелых приложениях инициализация выносится в отдельный модуль bootstrap:
import localForage from "localforage";
let storageReadyPromise = null;
export function initStorage() {
if (!storageReadyPromise) {
storageReadyPromise = localForage.ready();
}
return storageReadyPromise;
}
Такой подход предотвращает повторную инициализацию и гарантирует единый lifecycle хранилища во всём приложении.
Прямое использование API часто заменяется обёрткой, которая инкапсулирует инициализацию:
import localForage from "localforage";
class StorageService {
constructor() {
this.readyPromise = null;
}
init() {
if (!this.readyPromise) {
this.readyPromise = localForage.ready();
}
return this.readyPromise;
}
getItem(key) {
return localForage.getItem(key);
}
setItem(key, value) {
return localForage.setItem(key, value);
}
}
export const storage = new StorageService();
Такой слой позволяет:
В одностраничных приложениях критично разделять этапы:
Пример последовательности:
import { initStorage } from "./storage";
import { hydrateStore } from "./store";
async function bootstrap() {
await initStorage();
await hydrateStore();
startApp();
}
bootstrap();
Любая попытка рендера до завершения initStorage()
создаёт риск несогласованного состояния между UI и persisted state.
При использовании Redux-подобных или Vuex-подобных систем storage инициализируется до восстановления state:
await localForage.ready();
const savedState = await localForage.getItem("app_state");
store.replaceState(savedState || initialState);
Важно, что восстановление состояния не должно происходить до гарантированной готовности backend, иначе возможны частичные чтения или null-результаты из-за неинициализированного драйвера.
localForage зависит от браузерных API, поэтому при SSR необходимо разделять инициализацию:
const isBrowser = typeof window !== "undefined";
export async function initStorageSafe() {
if (!isBrowser) return false;
await localForage.ready();
return true;
}
Это предотвращает попытки обращения к IndexedDB в Node.js-окружении, где соответствующие API отсутствуют.
Инициализация часто включает проверку версии схемы данных:
async function migrate() {
const version = await localForage.getItem("schema_version");
if (!version) {
await localForage.setItem("schema_version", 1);
}
if (version === 1) {
const oldData = await localForage.getItem("cache");
await localForage.setItem("cache_v2", transform(oldData));
await localForage.removeItem("cache");
await localForage.setItem("schema_version", 2);
}
}
Такая логика должна выполняться строго после ready(),
иначе возможна потеря данных из-за обращения к неинициализированному
backend.
В реальных приложениях storage редко инициализируется изолированно:
await Promise.all([
localForage.ready(),
initApiClient(),
loadConfig()
]);
Однако такой подход требует гарантии, что ни один сервис не обращается к storage до завершения общего Promise. Нарушение этого правила приводит к race condition на уровне приложения.
localForage не предназначен для повторного «пересоздания» после старта приложения. Однако архитектурные ошибки часто приводят к повторным вызовам конфигурации.
Защита реализуется через модульный singleton:
let initialized = false;
export async function safeInit() {
if (initialized) return;
await localForage.ready();
initialized = true;
}
Для диагностики часто добавляют контрольные точки:
async function initStorageWithLogs() {
console.log("storage:init:start");
await localForage.ready();
console.log("storage:init:ready");
const driver = localForage.driver();
console.log("storage:driver:", driver);
}
Это позволяет выявлять случаи fallback на localStorage или неожиданный выбор WebSQL в старых браузерах.
При наличии нескольких независимых модулей (например, кэш, офлайн-очередь, настройки) важно избегать создания нескольких экземпляров:
import localForage from "localforage";
const cacheStore = localForage.createInstance({
name: "myApp",
storeName: "cache"
});
const settingsStore = localForage.createInstance({
name: "myApp",
storeName: "settings"
});
Каждый экземпляр требует собственной инициализации
ready(), что необходимо учитывать при старте
приложения:
await Promise.all([
cacheStore.ready(),
settingsStore.ready()
]);
Типичная проблема инициализации — обращение к storage до завершения bootstrap:
Все эти ошибки не связаны с самим API localForage напрямую, а являются следствием неправильного порядка инициализации в приложении.