Одной из ключевых особенностей библиотеки 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.
IndexedDB была разработана как современная замена ограничениям localStorage и предоставляет:
Однако API IndexedDB изначально построен на асинхронной модели.
Пример обычной операции IndexedDB:
const request = store.get("user");
request.onsucc ess = function() {
console.log(request.result);
};
Результат появляется не сразу, а после завершения внутренней операции браузера.
Поскольку localForage использует IndexedDB как основной драйвер, библиотека наследует его асинхронный характер.
localForage поддерживает несколько драйверов:
Настройка происходит автоматически:
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 до завершения сохранения.
Асинхронный механизм позволяет выполнять подобные задачи без заморозки интерфейса.
Большинство современных браузерных API используют асинхронную модель:
Например:
const response = await fetch("/api/users");
const users = await response.json();
Работа с localForage выглядит аналогично:
const users = await localforage.getItem("users");
Благодаря этому код становится более единообразным.
Каждый основной метод localForage возвращает Promise.
Пример:
const promise = localforage.getItem("user");
Типичный результат:
promise.then(user => {
console.log(user);
});
Или современный вариант:
const user = await localforage.getItem("user");
Возвращение Promise позволяет:
Асинхронность дает возможность запускать несколько задач параллельно.
Например:
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);
}
Такая модель хорошо масштабируется при построении крупных приложений.
На первый взгляд может показаться, что библиотека могла бы предоставлять оба варианта:
const user = localforage.getItemSync("user");
Однако реализация подобного подхода столкнулась бы с серьезными ограничениями.
Основные проблемы:
Поэтому разработчики выбрали единственный универсальный вариант — асинхронное API.
Рассмотрим пример:
const user = await localforage.getItem("user");
Последовательность действий обычно выглядит следующим образом:
fulfilled.await получает значение.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.