Почему localForage работает асинхронно

Одной из ключевых особенностей библиотеки localForage является полностью асинхронный интерфейс работы с данными. Все основные операции — сохранение, чтение, удаление, перебор записей и очистка хранилища — выполняются через Promise или колбэки и не возвращают результат немедленно.

Такой подход отличается от стандартного localStorage, где данные доступны синхронно:

localStorage.setItem("user", "Alex");
const user = localStorage.getItem("user");

В localForage аналогичная операция выглядит иначе:

await localforage.setItem("user", "Alex");

const user = await localforage.getItem("user");

Асинхронность не является случайным дизайнерским решением. Она напрямую связана с архитектурой браузерных механизмов хранения данных и задачами, которые решает библиотека.


Проблемы синхронного хранения данных

Синхронный код блокирует выполнение JavaScript до завершения операции.

Рассмотрим пример:

const data = localStorage.getItem("settings");

console.log("Настройки загружены");

Пока браузер получает данные, выполнение следующей строки останавливается.

Для небольших строк это выглядит безобидно, однако при работе с большими объемами данных возникают проблемы:

  • зависание интерфейса;
  • задержка обработки событий;
  • торможение анимаций;
  • ухудшение пользовательского опыта.

Если приложение хранит несколько мегабайт данных и выполняет многочисленные операции чтения, синхронная модель начинает создавать заметные задержки.


Основа localForage — IndexedDB

Главная причина асинхронности заключается в том, что localForage чаще всего работает поверх технологии IndexedDB.

IndexedDB была разработана как современная замена ограничениям localStorage и предоставляет:

  • хранение больших объемов информации;
  • работу с бинарными объектами;
  • индексацию данных;
  • транзакции;
  • высокую производительность.

Однако API IndexedDB изначально построен на асинхронной модели.

Пример обычной операции IndexedDB:

const request = store.get("user");

request.onsucc ess = function() {
    console.log(request.result);
};

Результат появляется не сразу, а после завершения внутренней операции браузера.

Поскольку localForage использует IndexedDB как основной драйвер, библиотека наследует его асинхронный характер.


Унификация различных хранилищ

localForage поддерживает несколько драйверов:

  • IndexedDB;
  • WebSQL;
  • LocalStorage.

Настройка происходит автоматически:

localforage.config({
    name: "MyApp"
});

При запуске библиотека выбирает наиболее подходящий механизм хранения.

Проблема заключается в том, что IndexedDB и WebSQL работают асинхронно, а localStorage — синхронно.

Если бы API localForage зависел от конкретного драйвера, код приложения выглядел бы по-разному в разных браузерах.

Для устранения этой проблемы разработчики выбрали единый асинхронный интерфейс:

await localforage.getItem("user");

Независимо от того, используется IndexedDB, WebSQL или LocalStorage, вызов всегда остается одинаковым.


Отсутствие блокировки главного потока

JavaScript в браузере работает преимущественно в одном потоке.

Этот поток отвечает сразу за несколько задач:

  • выполнение скриптов;
  • обработку событий мыши;
  • обработку клавиатуры;
  • отрисовку интерфейса;
  • анимации.

Если длительная операция выполняется синхронно, главный поток временно блокируется.

Асинхронная модель localForage позволяет избежать подобной ситуации:

const promise = localforage.getItem("products");

console.log("Интерфейс продолжает работать");

Пока данные загружаются, браузер может:

  • реагировать на действия пользователя;
  • обновлять страницу;
  • выполнять другие задачи.

В результате приложение выглядит более отзывчивым.


Работа с большими объектами

localStorage фактически предназначен для хранения строк.

localForage способен сохранять гораздо более сложные структуры:

await localforage.setItem("profile", {
    id: 1,
    name: "Alex",
    settings: {
        theme: "dark"
    }
});

Также поддерживаются бинарные данные:

await localforage.setItem("image", blob);

Запись крупных объектов может занимать заметное время.

Если бы такая операция выполнялась синхронно, браузер был бы вынужден остановить выполнение JavaScript до завершения сохранения.

Асинхронный механизм позволяет выполнять подобные задачи без заморозки интерфейса.


Согласованность с современным JavaScript

Большинство современных браузерных API используют асинхронную модель:

  • Fetch API;
  • IndexedDB;
  • Cache API;
  • Service Workers;
  • File System API.

Например:

const response = await fetch("/api/users");
const users = await response.json();

Работа с localForage выглядит аналогично:

const users = await localforage.getItem("users");

Благодаря этому код становится более единообразным.


Promise как основа API

Каждый основной метод localForage возвращает Promise.

Пример:

const promise = localforage.getItem("user");

Типичный результат:

promise.then(user => {
    console.log(user);
});

Или современный вариант:

const user = await localforage.getItem("user");

Возвращение Promise позволяет:

  • ожидать завершения операции;
  • обрабатывать ошибки;
  • комбинировать несколько запросов;
  • использовать возможности async/await.

Выполнение нескольких операций одновременно

Асинхронность дает возможность запускать несколько задач параллельно.

Например:

const userPromise = localforage.getItem("user");
const settingsPromise = localforage.getItem("settings");
const productsPromise = localforage.getItem("products");

const [user, settings, products] =
    await Promise.all([
        userPromise,
        settingsPromise,
        productsPromise
    ]);

Вместо последовательного ожидания каждой операции браузер может обрабатывать запросы более эффективно.


Обработка ошибок в асинхронной модели

При работе с хранилищем возможны различные проблемы:

  • превышение квоты хранения;
  • повреждение базы данных;
  • отказ драйвера;
  • ошибки сериализации.

Асинхронный интерфейс позволяет удобно перехватывать такие ситуации.

Через Promise:

localforage.getItem("user")
    .then(user => {
        console.log(user);
    })
    .catch(error => {
        console.error(error);
    });

Через async/await:

try {
    const user = await localforage.getItem("user");
    console.log(user);
} catch (error) {
    console.error(error);
}

Такая модель хорошо масштабируется при построении крупных приложений.


Почему нельзя просто сделать синхронный API

На первый взгляд может показаться, что библиотека могла бы предоставлять оба варианта:

const user = localforage.getItemSync("user");

Однако реализация подобного подхода столкнулась бы с серьезными ограничениями.

Основные проблемы:

  1. IndexedDB не предоставляет синхронный интерфейс для обычных веб-страниц.
  2. Разные драйверы имели бы разные модели работы.
  3. Код становился бы менее переносимым.
  4. Появлялась бы вероятность блокировки интерфейса.
  5. Производительность ухудшалась бы на больших объемах данных.

Поэтому разработчики выбрали единственный универсальный вариант — асинхронное API.


Что происходит внутри вызова getItem

Рассмотрим пример:

const user = await localforage.getItem("user");

Последовательность действий обычно выглядит следующим образом:

  1. Метод создает Promise.
  2. Выполняется обращение к активному драйверу.
  3. Драйвер инициирует чтение данных.
  4. JavaScript продолжает выполнение других задач.
  5. После получения результата Promise переходит в состояние fulfilled.
  6. Оператор await получает значение.
  7. Переменная user получает загруженные данные.

Со стороны разработчика операция выглядит почти как синхронная, однако фактически происходит неблокирующее взаимодействие с хранилищем.


Влияние на архитектуру приложений

Асинхронность localForage оказывает заметное влияние на организацию кода.

Вместо такого подхода:

const settings = storage.getSettings();
render(settings);

используется модель ожидания результата:

const settings = await storage.getSettings();
render(settings);

Или:

storage.getSettings()
    .then(settings => {
        render(settings);
    });

Из-за этого логика приложения строится вокруг событий завершения операций, что соответствует современной архитектуре веб-приложений.


Преимущества асинхронного подхода

Отсутствие блокировки интерфейса

Приложение остается отзывчивым даже при интенсивной работе с хранилищем.

Поддержка больших объемов данных

Можно безопасно хранить крупные объекты и бинарные файлы.

Совместимость с IndexedDB

Используются возможности наиболее мощного браузерного механизма хранения.

Единый интерфейс

Одинаковый код работает независимо от выбранного драйвера.

Интеграция с async/await

Код остается читаемым несмотря на асинхронную природу операций.

Лучшее масштабирование

При росте объема данных производительность и стабильность приложения сохраняются значительно лучше, чем при использовании исключительно синхронного localStorage.