IndexedDB в JavaScript — как использовать и работать с базой

В этом руководстве вы узнаете об IndexedDB в JavaScript и о том, как использовать и работать с ней для постоянного хранения данных внутри браузера.

IndexedDB в JavaScript — это крупномасштабное хранилище объектов, встроенное в браузер.

IndexedDB позволяет постоянно хранить данные, используя пары ключ-значение.

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

Почему indexedDB

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

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

Например, Google Docs использует IndexedDB для хранения кэшированных документов в браузере и время от времени синхронизируется с сервером. Это позволяет Google Docs повысить производительность и улучшить взаимодействие с пользователем.

Вы найдете и другие типы приложений, которые интенсивно используют IndexedDB, такие как онлайн-блокноты, викторины, списки задач, песочницы кода и CMS.

Структура

На следующем рисунке показана структура:

Структура IndexedDB

Базы данных

База данных — это высший уровень IndexedDB. База данных содержит одно или несколько хранилищ объектов.

IndexedDB может иметь одну или несколько баз данных. Как правило, вы создаете одну базу данных для каждого веб-приложения.

Хранилища объектов

Хранилище объектов — это сегмент, который можно использовать для хранения данных и связанных индексов. Концептуально это эквивалентно таблицам в базах данных SQL.

Хранилище объектов содержит записи, хранящиеся в виде пар ключ-значение.

Индексы

Индексы позволяют запрашивать данные по свойствам объектов.

Технически вы создаете индексы для хранилищ объектов, которые называются родительскими хранилищами объектов.

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

Основные понятия

Далее кратко представлены основные понятия IndexedDB:

1) Базы данных хранят пары ключ-значение

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

Кроме того, ключи могут быть свойствами этих объектов или могут быть бинарными объектами.

Для быстрого поиска и сортировки можно создавать индексы, использующие любое свойство объектов.

2) IndexedDB является транзакционной

Каждое чтение и запись в базы данных IndexedDB всегда происходит в транзакции.

Транзакционная модель обеспечивает целостность данных в случае, если пользователи одновременно открывают веб-приложение в двух вкладках/окнах и выполняют чтение и запись в одну и ту же базу данных.

3) API в основном асинхронный

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

4) Система NoSQL

IndexedDB — это система NoSQL. Другими словами, она не использует SQL для запроса данных. Вместо этого использует запрос, возвращающий курсор. Затем вы можете использовать курсор для повторения набора результатов.

5) Следует политике того же происхождения

Источник — это домен, протокол и порт URL-адреса документа, в котором выполняется код. Например https://www.js-ts-node.github.io:

  • домен: js-ts-node.github.io
  • протокол: https
  • порт: 443

https://js-ts-node.github.io/javascript-dom и https://js-ts-node.github.io/ имеют одинаковое происхождение, поскольку имеют одинаковый домен, протокол и порт.

Однако https://js-ts-node.github.io/ и https://js-ts-node.github.io/ не являются одним и тем же источником, поскольку у них разные протоколы и порты:

https://js-ts-node.github.io/https://js-ts-node.github.io/
Протоколhttpshttp
Порт44380

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

Основные операции

Ниже описаны основные операции с базами данных IndexedDB, такие как

  • Открытие соединения с базой данных.
  • Вставка объекта в хранилище объектов.
  • Чтение данных из хранилища объектов.
  • Использование курсора для перебора результирующего набора.
  • Удаление объекта из хранилища объектов.

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

 Создание структуры проекта

Сначала создайте новую папку с именем indexeddb. Внутри папки создайте еще одну подпапку с именем js.

Во-вторых, создайте index.html в папке indexeddb, app.js в папке js.

В-третьих, поместите <script>,  который ссылается на файл app.js, в файл index.html следующим образом:

 <!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>IndexedDB</title>
</head>
<body>
    <script src="js/app.js"></script>
</body>
</html>

В app.js вы поместите весь код JavaScript в IIFE.

(function() {
 // all the code will be here
 // ...
 })();

1) Проверьте, поддерживается ли

Следующий код проверяет, поддерживает ли веб-браузер IndexedDB:

if(!window.indexedDB) {
 console.log(`Your browser doesn't support IndexedDB`);
 return;
 }

Поскольку большинство современных веб-браузеров поддерживают IndexedDB, в этом больше нет необходимости.

2) Откройте базу данных

Чтобы открыть соединение с базой данных, используйте метод open() window.indexedDB :

const request = indexedDB.open('CRM', 1);

Метод open() принимает два аргумента:

  • Имя базы данных( CRM )
  • Версия базы данных( 1 )

Метод open() возвращает объект запроса, который является экземпляром интерфейса IDBOpenDBRequest.

Когда вы вызываете метод open(), он может быть успешным или неудачным. Для обработки каждого случая вы можете назначить соответствующий обработчик событий следующим образом:

request.onerror = (event) = >{
    console.error(`Database error: $ {
        event.target.errorCode
    }`);
};
request.onsuccess = (event) = >{ // add implementation here };

3) Создайте хранилища объектов

Когда вы открываете базу данных в первый раз, срабатывает событие onupgradeneeded.

Если вы открываете базу данных во второй раз с версией выше существующей, также срабатывает событие onupgradeneeded.

В первый раз вы можете использовать обработчик событий onupgradeneeded для инициализации хранилищ объектов и индексов.

Например, следующий обработчик событий onupgradeneeded создает хранилище объектов Contacts и его индекс.

// create the Contacts object store and indexes
 request.onupgradeneeded =(event) => {
 let db = event.target.result;

 // create the Contacts object store
 // with auto-increment id
 let store = db.createObjectStore('Contacts', {
 autoIncrement: true
 });

 // create an index on the email property
 let index = store.createIndex('email', 'email', {
 unique: true
 });
 };

Как это работает:

  • Сначала получите экземпляр IDBDatabase из event.target.result и назначьте его переменной db.
  • Во-вторых, вызовите метод createObjectStore(), чтобы создать хранилище объектов Contacts с ключом autoincrement. Это означает, что IndexedDB будет генерировать автоматически увеличивающийся номер, начиная с единицы, в качестве ключа для каждого нового объекта, вставленного в хранилище объектов Contacts.
  • В-третьих, вызовите метод createIndex(), чтобы создать индекс для свойства email. Поскольку электронная почта уникальна, индекс также должен быть уникальным. Для этого вы указываете третий аргумент метода createIndex() { unique: true }.

4) Вставить данные в хранилища объектов

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

Например, чтобы добавить объект в хранилище объектов, выполните следующие действия.

  • Сначала откройте новую транзакцию.
  • Во-вторых, получите хранилище объектов.
  • В-третьих, вызовите метод put() хранилища объектов, чтобы вставить новую запись.
  • Наконец, закройте соединение с базой данных после завершения транзакции.

Следующая insertContact() вставляет новый контакт в хранилище объектов Contacts :

function insertContact(db, contact) {
 // create a new transaction
 const txn = db.transaction('Contacts', 'readwrite');

 // get the Contacts object store
 const store = txn.objectStore('Contacts');
 //
 let query = store.put(contact);

 // handle success case
 query.onsuccess = function(event) {
 console.log(event);
 };

 // handle the error case
 query.onerror = function(event) {
 console.log(event.target.errorCode);
 }

 // close the database once the
 // transaction completes
 txn.oncomplete = function() {
 db.close();
 };
 }

Чтобы создать новую транзакцию, вы вызываете метод transaction() объекта IDBDatabase.

Вы можете открыть транзакцию в одном из двух режимов: readwrite или readonly. Режим readwrite и записи позволяет вам читать данные из базы данных и записывать данные в нее, а readonly только для чтения позволяет вам только читать данные из базы данных.

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

После определения функции insertContact() вы можете вызвать ее в обработчике события onsuccess запроса, чтобы вставить один или несколько контактов следующим образом:

request.onsuccess = (event) = >{
    const db = event.target.result;
    insertContact(db, {
        email: 'john.doe@outlook.com',
        firstName: 'John',
        lastName: 'Doe'
    });
    insertContact(db, {
        email: 'jane.doe@gmail.com',
        firstName: 'Jane',
        lastName: 'Doe'
    });
};

Теперь, если вы откроете файл index.html в веб-браузере, код в app.js нужно выполнять так:

  • Создайте базу данных CRM в IndexedDB.
  • Создайте хранилище объектов « Contacts » в базе данных CRM.
  • Вставьте две записи в хранилище объектов.

Если вы откроете devtools в веб-браузере, вы увидите базу данных CRM с хранилищем объектов Contacts. А в хранилище объектов « Contacts » вы увидите данные, как показано на следующем рисунке:

База данных CRM с хранилищем объектов Contacts

5) Чтение данных из хранилища объектов по ключу

Чтобы прочитать объект по его ключу, вы используете метод get() хранилища объектов. Следующая getContactById() находит контакт по идентификатору:

function getContactById(db, id) {
    const txn = db.transaction('Contacts', 'readonly');
    const store = txn.objectStore('Contacts');
    let query = store.get(id);
    query.onsuccess = (event) = >{
        if (!event.target.result) {
            console.log(`The contact with $ {
                id
            }
            not found`);
        } else {
            console.table(event.target.result);
        }
    };
    query.onerror = (event) = >{
        console.log(event.target.errorCode);
    }
    txn.oncomplete = function() {
        db.close();
    };
};

Когда вы вызываете метод get() хранилища объектов, он возвращает запрос, который будет выполняться асинхронно.

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

Если запрос выполнен успешно, вы получите результат в event.target.result. В противном случае вы получите код ошибки через event.target.errorCode.

Следующий код закрывает соединение с базой данных после завершения транзакции:

txn.oncomplete = function() {
 db.close();
 };

На самом деле соединение с базой данных закрывается только тогда, когда все созданные с его помощью транзакции завершены.

Следующее вызывает getContactById() в обработчике события onsuccess, чтобы получить контакт с идентификатором 1:

request.onsuccess =(event) => {
 const db = event.target.result;
 getContactById(db, 1);
 };

Выход:

6) Чтение данных из хранилища объектов по индексу

Ниже определяется новая функция getContactByEmail(), которая использует индекс электронной почты для запроса данных:

function getContactByEmail(db, email) {
 const txn = db.transaction('Contacts', 'readonly');
 const store = txn.objectStore('Contacts');

 // get the index from the Object Store
 const index = store.index('email');
 // query by indexes
 let query = index.get(email);

 // return the result object on success
 query.onsuccess =(event) => {
 console.log(query.result); // result objects
 };

 query.onerror =(event) => {
 console.log(event.target.errorCode);
 }

 // close the database connection
 txn.oncomplete = function() {
 db.close();
 };
 }

Как это работает:

  • Сначала получите объект индекса электронной почты из хранилища объектов « Contacts ».
  • Во-вторых, используйте индекс для чтения данных, вызвав метод get().
  • В-третьих, покажите результат консоли в обработчике события onsuccess запроса.

Ниже показано, как использовать getContactByEmail() в обработчике события onsuccess :

request.onsuccess =(event) => {
 const db = event.target.result;
 // get contact by email
 getContactByEmail(db, 'jane.doe@gmail.com');
 };

Выход:

7) Считывать все данные из хранилища объектов

Ниже показано, как использовать курсор для чтения всех объектов из хранилища объектов « Contacts ».

function getAllContacts(db) {
 const txn = db.transaction('Contacts', "readonly");
 const objectStore = txn.objectStore('Contacts');

 objectStore.openCursor().onsuccess =(event) => {
 let cursor = event.target.result;
 if(cursor) {
 let contact = cursor.value;
 console.log(contact);
 // continue next record
 cursor.continue();
 }
 };
 // close the database connection
 txn.oncomplete = function() {
 db.close();
 };
 }

objectStore.openCursor() возвращает курсор, используемый для итерации по хранилищу объектов.

Чтобы перебирать объекты в хранилище объектов с помощью курсора, вам нужно назначить обработчик onsuccess:

objectStore.openCursor().onsuccess =(event) => {
 //...
 };

Event.target.result возвращает курсор. Чтобы получить данные, вы используете свойство cursor.value.

Метод cursor.continue() перемещает курсор на позицию следующей записи в хранилище объектов.

Следующее вызывает getAllContacts() в обработчике событий onsuccess, чтобы отобразить все данные из хранилища объектов Contacts :

request.onsuccess =(event) => {
 const db = event.target.result;
 // get all contacts
 getAllContacts(db);
 };

Выход:

8) Удалить контакт

Чтобы удалить запись из хранилища объектов, вы используете метод delete() хранилища объектов.

Следующая функция удаляет контакт по его идентификатору из хранилища объектов « Contacts »:

function deleteContact(db, id) {
 // create a new transaction
 const txn = db.transaction('Contacts', 'readwrite');

 // get the Contacts object store
 const store = txn.objectStore('Contacts');
 //
 let query = store.delete(id);

 // handle the success case
 query.onsuccess = function(event) {
 console.log(event);
 };

 // handle the error case
 query.onerror = function(event) {
 console.log(event.target.errorCode);
 }

 // close the database once the
 // transaction completes
 txn.oncomplete = function() {
 db.close();
 };
 }

И вы можете вызвать deleteContact() в обработчике события onsuccess, чтобы удалить контакт с идентификатором 1 следующим образом:

request.onsuccess =(event) => {
 const db = event.target.result;
 deleteContact(db, 1);
 };

Если вы запустите код, вы обнаружите, что контакт с идентификатором 1 будет удален.

Итоги проекта

Ниже показан полный файл app.js:

(function() {
 // check for IndexedDB support
 if(!window.indexedDB) {
 console.log(`Your browser doesn't support IndexedDB`);
 return;
 }

 // open the CRM database with the version 1
 const request = indexedDB.open('CRM', 1);

 // create the Contacts object store and indexes
 request.onupgradeneeded =(event) => {
 let db = event.target.result;

 // create the Contacts object store
 // with auto-increment id
 let store = db.createObjectStore('Contacts', {
 autoIncrement: true
 });

 // create an index on the email property
 let index = store.createIndex('email', 'email', {
 unique: true
 });
 };

 // handle the error event
 request.onerror =(event) => {
 console.error(`Database error: ${event.target.errorCode}`);
 };

 // handle the success event
 request.onsuccess =(event) => {
 const db = event.target.result;

 // insert contacts
 // insertContact(db, {
 // email: 'john.doe@outlook.com',
 // firstName: 'John',
 // lastName: 'Doe'
 // });

 // insertContact(db, {
 // email: 'jane.doe@gmail.com',
 // firstName: 'Jane',
 // lastName: 'Doe'
 // });


 // get contact by id 1
 // getContactById(db, 1);


 // get contact by email
 // getContactByEmail(db, 'jane.doe@gmail.com');

 // get all contacts
 // getAllContacts(db);

 deleteContact(db, 1);

 };

 function insertContact(db, contact) {
 // create a new transaction
 const txn = db.transaction('Contacts', 'readwrite');

 // get the Contacts object store
 const store = txn.objectStore('Contacts');
 //
 let query = store.put(contact);

 // handle success case
 query.onsuccess = function(event) {
 console.log(event);
 };

 // handle the error case
 query.onerror = function(event) {
 console.log(event.target.errorCode);
 }

 // close the database once the
 // transaction completes
 txn.oncomplete = function() {
 db.close();
 };
 }


 function getContactById(db, id) {
 const txn = db.transaction('Contacts', 'readonly');
 const store = txn.objectStore('Contacts');

 let query = store.get(id);

 query.onsuccess =(event) => {
 if(!event.target.result) {
 console.log(`The contact with ${id} not found`);
 } else {
 console.table(event.target.result);
 }
 };

 query.onerror =(event) => {
 console.log(event.target.errorCode);
 }

 txn.oncomplete = function() {
 db.close();
 };
 };

 function getContactByEmail(db, email) {
 const txn = db.transaction('Contacts', 'readonly');
 const store = txn.objectStore('Contacts');

 // get the index from the Object Store
 const index = store.index('email');
 // query by indexes
 let query = index.get(email);

 // return the result object on success
 query.onsuccess =(event) => {
 console.table(query.result); // result objects
 };

 query.onerror =(event) => {
 console.log(event.target.errorCode);
 }

 // close the database connection
 txn.oncomplete = function() {
 db.close();
 };
 }



 function deleteContact(db, id) {
 // create a new transaction
 const txn = db.transaction('Contacts', 'readwrite');

 // get the Contacts object store
 const store = txn.objectStore('Contacts');
 //
 let query = store.delete(id);

 // handle the success case
 query.onsuccess = function(event) {
 console.log(event);
 };

 // handle the error case
 query.onerror = function(event) {
 console.log(event.target.errorCode);
 }

 // close the database once the
 // transaction completes
 txn.oncomplete = function() {
 db.close();
 };

 }
 })();

Заключение

  • IndexedDB — это крупномасштабное хранилище объектов, встроенное в веб-браузеры.
  • Хранит данные в виде пар ключ-значение. В качестве значений могут выступать любые данные, в том числе простые и сложные.
  • IndexedDB в Javascript состоит из одной или нескольких баз данных. Каждая база данных имеет одно или несколько хранилищ объектов. Как правило, вы создаете базу данных в IndexedDB для каждого веб-приложения.
  • Полезен для веб-приложений, которым не требуется постоянное подключение к Интернету, особенно для приложений, которые работают как в сети, так и в автономном режиме.
Рейтинг
( Пока оценок нет )
Александр Русаков / автор статьи
Программист, разработчик, 12 лет опыта работы в крупных компаниях. Быстро освоил typescript, делюсь своими знаниями на страницах этого сайта.
Загрузка ...
JavaScript и TypeScript