Нордавинд
Компания
43,02
рейтинг
15 февраля 2014 в 20:54

Разработка → LocalForage: кроссбраузерное локальное хранилище от Mozilla перевод tutorial

Уже довольно давно у веб-приложений появилась возможность хранить часть данных или файлов локально. Можно даже кешировать MP3-файлы. Браузеры научились хранить немалые объёмы данных. Тем не менее, пока что технологии локального хранения сильно фрагментированы.

localStorage предоставляет лишь самые базовые функции, это хранилище довольно медленно и не умеет хранить блобы. IndexedDB и WebSQL асинхронны, быстры и поддерживают большие объемы данных, но их API довольно запутан. Кроме того, ни IndexedDB, ни WebSQL не поддерживаются всеми основными браузерами, и, похоже, в ближайшем будущем эта ситуация не изменится.

Если вам нужно писать веб-приложение с оффлайн-режимом, и вы не знаете, с чего начать, то эта статься для вас. Если вы уже пытались работать с локальными хранилищами, и у вас от этого голова пошла кругом — статья и для вас тоже. Мы в Mozilla написали библиотеку localForage, которая значительно облегчает задачу хранения локальных данных в любом браузере.

Почувствовать на своей шкуре все сложности работы с локальным хранилищем мне помогла разработка around — HTML5-клиента для Foursquare. Хотя в этой статье я рассказываю, как использовать localForage, возможно кто-то предпочтёт изучить реальные примеры работы с ней.

localForage — очень простая библиотека JavaScript, которая использует API, похожий на API localStorage, с теми же самыми базовыми методами get, set, remove, clear и length, но имеет ещё несколько важных улучшений:

  • асинхронный API с колбэками;
  • драйвера IndexedDB, WebSQL и localStorage (самый подходящий драйвер выбирается автоматически в зависимости от возможностей браузера);
  • поддержка блобов и произвольных форматов данных, так что можно хранить изображения, файлы и так далее;
  • поддержка обещаний ECMAScript 6.

Использование IndexedDB и WebSQL позволяет хранить намного больше данных, чем localStorage. Неблокирующий асинхронный API делает приложение более быстрым и отзывчивым, так как основной поток приложения не подвисает во время выполнения вызовов get/set. Поддержка обещаний позволяет писать чистый код без спагетти из колбэков. Конечно, если вы любите колбэки, можно использовать и их.

Хватит болтовни, покажите, как это работает!


Традиционный API localStorage во многих отношениях очень неплох. Он прост, не навязывает сложные структуры данных и не требует вообще никакого boilerplate-кода. Например, если вам нужно хранить локально конфигурацию приложения, вы можете написать что-то вроде:

// Это информация, которую мы хотим хранить локально
var config = {
    fullName: document.getElementById('name').getAttribute('value'),
    userId: document.getElementById('id').getAttribute('value')
};
 
// Сохранение конфигурации
localStorage.setItem('config', JSON.stringify(config));
 
// Восстановление при следующей загрузке приложения
var config = JSON.parse(localStorage.getItem('config'));

Обратите внимание, что localStorage хранит всю информацию в виде строк, поэтому приходится преобразовывать всё в сериализованный JSON.

Всё очень просто и логично, но сразу можно заметить несколько проблем:

  • Cинхронность. Приходится ждать, пока данные будут считаны с диска и обработаны парсером, какого бы размера они ни были. Это ухудшает отзывчивость приложения. Особенно плохо это на мобильных устройствах — блокируется главный потоrу выполнения, пока идёт обращение к данным, из-за чего приложение начинает казаться медленным и подвисающим.
  • Всё хранится в виде строк. Нам постоянно приходится использовать JSON.parse и JSON.stringify. Это потому, что localStorage умеет работать только со строками JavaScript. Никаких чисел, булевых значений, блобов и прочего. Это раздражает при необходимости хранить числа или массивы, а работу с бинарными данными делает практически невозможной (или, по крайней мере чудовищно медленной).

Упрощаем жизнь с помощь localForage


localForage решает обе эти проблемы с помощью API, очень похожего на интерфейс localStorage, но асинхронного. Сравните, насколько он проще эквивалентного кода для IndexedDB.

Код IndexedDB:

// IndexedDB.
var db;
var dbName = "dataspace";
 
var users = [ {id: 1, fullName: 'Matt'}, {id: 2, fullName: 'Bob'} ];
 
var request = indexedDB.open(dbName, 2);
 
request.onerror = function(event) {
    // Обработка ошибок.
};
request.onupgradeneeded = function(event) {
    db = event.target.result;
 
    var objectStore = db.createObjectStore("users", { keyPath: "id" });
 
    objectStore.createIndex("fullName", "fullName", { unique: false });
 
    objectStore.transaction.oncomplete = function(event) {
        var userObjectStore = db.transaction("users", "readwrite").objectStore("users");
    }
};
 
// После того, как БД создана, добавим туда запись о пользователе
 
var transaction = db.transaction(["users"], "readwrite");
 
// Как-то отреагируем на окончание процесса записи в базу
transaction.oncomplete = function(event) {
    console.log("All done!");
};
 
transaction.onerror = function(event) {
    // Не забываем обрабатывать ошибки
};
 
var objectStore = transaction.objectStore("users");
 
for (var i in users) {
    var request = objectStore.add(users[i]);
    request.onsuccess = function(event) {
        // Выведем в консоль информацию о каждом добавленном пользователе
        console.log(event.target.result);
    };
}


Код localForage:

// Сохраняем информацию о пользователях
var users = [ {id: 1, fullName: 'Matt'}, {id: 2, fullName: 'Bob'} ];
localForage.setItem('users', users, function(result) {
    console.log(result);
});

Код с WebSQL будет несколько короче, чем код с IndexedDB, но всё равно потребует гораздо больше текста, чем с localForage.

Не только строки


Допустим, вы хотите использовать локально или не только текстовые данные о пользователе, но и его аватарку. С localForage это делается очень просто:

// Загружаем фотографию AJAX-запросом
var request = new XMLHttpRequest();
 
// Допустим, нам нужна фотогрфия пользователя с id=1
request.open('GET', "/users/1/profile_picture.jpg", true);
request.responseType = 'arraybuffer';
 
// Когда запрос завершится, сохраним фото локально
request.addEventListener('readystatechange', function() {
    if (request.readyState === 4) { // readyState DONE
        // Сохраняем данные как есть. С localStorage такое невозможно
        localForage.setItem('user_1_photo', request.response, function() {
            // Фото сохранилось, работаем дальше.
        });
    }
});
 
request.send();

Извлечь фотографию из хранилища можно с помощью всего трёх строк кода:

localForage.getItem('user_1_photo', function(photo) {
    // Создаём data URI или ещё как-нибудь помещаем фото в тег img.
    console.log(photo);
});

Колбэки и обещания


Если вы не любите колбэки, вы можете использовать обещания ES6. Вот так будет выглядеть последний пример, если переписать его с использованием обещаний:

localForage.getItem('user_1_photo').then(function(photo) {
    // Создаём data URI или ещё как-нибудь помещаем фото в тег img.
    console.log(photo);
});

Конечно, это искусственный и не слишком наглядный пример. Если вы хотите посмотреть на такой стиль программирования в реальном коде — вот подходящий фрагмент из around.

Кроссбраузерность


localForage поддерживает все современные браузеры. IndexedDB доступна во всех современных браузерах кроме Safari ((IE 10+, IE Mobile 10+, Firefox 10+, Firefox for Android 25+, Chrome 23+, Chrome for Android 32+, Opera 15+). Safari и штатный браузер Android (2.1+) используют WebSQL.

В самом крайнем случае, localForage использует localStorage, так что вы по-прежнему можете хранить данные локально, правда без блобов и гораздо медленнее. Но хотя бы преобразование данных в строки JSON происходит в этом случае автоматически.

Библиотека ещё очень молода, часть функционала только планируется, так что присоединяйтесь к разработке, присылайте сообщения об ошибках и патчи, если хотите, чтобы библиотека умела делать больше!

Автор: @ilya42 Matthew Riley MacPherson
Нордавинд
рейтинг 43,02
Компания прекратила активность на сайте

Комментарии (12)

  • 0
    Оно лучше, чем github.com/tantaman/LargeLocalStorage? Если да, то чем?

    P.S. Опять же — если хуже, то чем? :)
    • +10
      LargeLocalStorage и localForage сделаны для решения различных проблем.

      LargeLocalStorage предназначен для хранения больших значений. В решение данной проблемы помогает FilesystemAPI, но его поддержка на данный момент очень слабая.

      LargeLocalStorage предлагает fallback для FilesystemAPI с использованием IndexedDB/WebSQL/LocalStorage в зависимости от их доступности в браузере. LargeLocalStorage также позволяет реализовать свой интерфейс для работы с хранилищем.

      localForage — это решение для offline web app, которое позволяет работать с данными максимально быстро.
      localForage, как и LargeLocalStorage предоставляет fallback и они имеют следующий порядок применения: IndexedDB -> WebSQL -> LocalStorage. localForage избавляет вас от проблемы написания fallback'ов и предоставляет общий интерфейс к хранилищу.

      Наиболее производительным и масштабируемым является IndexedDB, но его поддержка еще довольно слаба.
      LocalStorage имеет ограничения по размеру хранилища(5-10 MB) и его скорость работы медленнее, чем у IndexedDB или WebSQL.
      WebSQL является ранним стандартом для IndexedDB и позднее от него отказались, но его поддержка в некоторых браузерах лучше, чем у IndexedDB.
      В случае отсутствия поддержки IndexedDB и WebSQL используется LocalStorage.

      Обе библиотеки полезны и решают различные проблемы.
  • +1
    От WebSQL вроде как отказались же w3?
    • 0
      От WebSQL отказались в пользу IndexedDB. Он используется из-за более широкой поддержки в браузерах, как fallback для IndexedDB.

      WebSQL: Chrome, Safari(iOS), Opera, Android.
      IndexedDB: Chrome, FF, IE, но отсутствует поддержка Safari и Android
  • 0
    Спасибо за статью, будем пробовать.
  • +1
    А что с нотификациями? Основная фича localStorage в возможности отслеживать изменения в хранилище.
    • 0
      А кто вам мешает использовать для нотификаций localStorage сам по себе? У этой библиотеки другие задачи.
      • 0
        А что мешает добавить этот функционал в предлагаемую библиотеку, раз уж она позиционирует себя как замена localStorage?
        • 0
          Ну там в конце поста пожелание присылать патчи, если хотите чтобы библиотека умела делать больше)
  • 0
    Индексирование и выборки, я так понимаю, отсутствуют? А так хотелось отвязаться от WebSQL…
  • 0
    Чем отличается от PouchDB?

    Если блобы не поддерживаются с localStorage бекендом, подерживаются ли они с WebSQL бекендом?
    • 0
      Код выглядит понятным и довольно компактен, а вот отсутсвие обработки ошибок (console.error) немного смущает.

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

Самое читаемое Разработка