Браузерное хранилище localStorage реализовано как
синхронный механизм ключ–значение, выполняющий операции непосредственно
в основном потоке JavaScript. Каждое обращение к чтению или записи
блокирует выполнение кода до завершения операции, что делает поведение
предсказуемым, но создаёт потенциальные проблемы производительности.
localStorage работает по строго последовательной схеме:
вызов метода выполняется немедленно, а результат доступен сразу после
его завершения. Это означает отсутствие очередей, отсутствия промисов и
отсутствия фоновой обработки.
Ключевые особенности:
Пример синхронной записи и чтения:
localStorage.setItem('user', JSON.stringify({ name: 'Alex' }));
const user = JSON.parse(localStorage.getItem('user'));
Каждый вызов выполняется немедленно. Даже при небольших объёмах данных это может создавать заметные задержки в интерфейсе при частых операциях.
Синхронная природа приводит к нескольким архитектурным ограничениям:
Любая операция записи или чтения останавливает выполнение JavaScript до завершения работы хранилища. При увеличении объёма данных или частых вызовах это может вызывать:
При хранении больших структур данных:
for (let i = 0; i < 10000; i++) {
localStorage.setItem(`key${i}`, JSON.stringify({ index: i }));
}
весь цикл выполняется в основном потоке, создавая заметную блокировку.
Синхронная модель не поддерживает параллельные операции. Любая попытка “одновременных” записей фактически превращается в последовательное выполнение.
В отличие от localStorage, современные механизмы
хранения, такие как IndexedDB, используют асинхронную модель. Операции
выполняются вне основного потока и возвращают результат через callback
или Promise.
Пример асинхронного подхода:
const request = indexedDB.open('app', 1);
request.onsucc ess = (event) => {
const db = event.target.result;
// операции выполняются через транзакции
};
Асинхронность позволяет:
Однако IndexedDB имеет сложный API, требующий управления транзакциями, курсорами и версиями схемы.
localForage решает ключевую проблему: необходимость унифицированного API поверх разных механизмов хранения с разной природой (синхронной и асинхронной).
Главная идея заключается в предоставлении единого асинхронного интерфейса, независимо от того, какой драйвер используется внутри:
Наиболее интересный технический момент заключается в том, что
синхронный localStorage превращается в асинхронный API.
Если один драйвер работает синхронно, а другой асинхронно, возникает несоответствие моделей:
const v = getItem(key)getItem(key).then(...)localForage решает это путём принудительной асинхронности интерфейса.
Даже если операция выполняется синхронно внутри
localStorage, результат всегда возвращается асинхронно
через Promise или callback.
Упрощённая модель:
function getItem(key) {
return new Promise((resolve) => {
const value = localStorage.getItem(key);
resolve(value);
});
}
На практике используется более сложная логика с очередями задач, но принцип остаётся:
Единая асинхронная модель даёт несколько архитектурных преимуществ.
Код, работающий с хранилищем, не зависит от выбранного драйвера:
await storage.setItem('key', value);
const result = await storage.getItem('key');
localForage гарантирует одинаковое поведение независимо от backend-а.
Даже если используется localStorage, асинхронная обёртка
предотвращает:
Если приложение позже переключается на IndexedDB, код не требует переписывания. Меняется только драйвер, но не API.
Архитектура localForage строится вокруг концепции драйверов:
localStorageWrapperindexedDBWrapperwebSQLWrapperКаждый драйвер реализует одинаковый интерфейс:
getItemsetItemremoveItemclearlengthНо способы выполнения операций различаются.
| Операция | localStorage | IndexedDB через localForage |
|---|---|---|
| setItem | мгновенно, блокирует поток | неблокирующая Promise |
| getItem | мгновенно | Promise |
| removeItem | мгновенно | Promise |
| clear | мгновенно | Promise |
При использовании синхронного backend-а важным элементом становится интеграция с event loop.
Даже если результат уже известен, он возвращается через асинхронный механизм:
Это создаёт единое поведение независимо от драйвера.
При работе с localStorage через localForage может
возникать иллюзия «настоящей» асинхронной работы, хотя внутри операция
выполняется синхронно.
Это важно учитывать:
Асинхронная обёртка добавляет минимальную задержку:
Однако этот оверхед компенсируется предсказуемостью и единообразием API.
При последовательных операциях:
await storage.setItem('a', 1);
await storage.setItem('b', 2);
await storage.setItem('c', 3);
localForage гарантирует:
Даже если backend синхронный, интерфейс остаётся асинхронным.
Различие между localStorage и IndexedDB не только в
объёме или скорости, но и в фундаментальной модели выполнения:
localStorage — синхронный, блокирующий, строковыйlocalForage объединяет эти модели, вводя единый асинхронный контракт, скрывающий различие между блокирующими и неблокирующими операциями за одинаковым API.