JavaScript IndexadoDB

Resumo : neste tutorial, você aprenderá sobre o IndexedDB e como usá-lo para armazenar dados persistentemente dentro do navegador.

O que é indexadoDB

IndexedDB é um armazenamento de objetos em grande escala integrado ao navegador.

O IndexedDB permite armazenar dados de forma persistente usando pares de valores-chave.

Os valores podem ser qualquer tipo de JavaScript , incluindo boolean , number , string , undefined , null, date, object , array , regex , blob e arquivos.

Por que indexadoDB

IndexedDB permite criar aplicativos da web que podem funcionar tanto online quanto offline.

É útil para aplicativos que armazenam uma grande quantidade de dados e não precisam de uma conexão persistente com a Internet.

Por exemplo, o Google Docs usa IndexedDB para armazenar os documentos armazenados em cache no navegador e sincronizar com o servidor de vez em quando. Isso permite que o Google Docs aumente o desempenho e melhore a experiência do usuário.

E você encontrará outros tipos de aplicativos que usam fortemente o IndexedDB, como blocos de notas online, questionários, listas de tarefas, sandboxes de código e CMS.

Estrutura IndexedDB

A imagem a seguir ilustra a estrutura do IndexedDB:

Bancos de dados

Um banco de dados é o nível mais alto de IndexedDB. Um banco de dados contém um ou mais armazenamentos de objetos.

O IndexedDB pode ter um ou mais bancos de dados. Geralmente, você criará um banco de dados por aplicativo Web.

Armazenamentos de objetos

Um armazenamento de objetos é um bucket que você pode usar para armazenar os dados e os índices associados. É conceitualmente equivalente às tabelas dos bancos de dados SQL.

Um armazenamento de objetos contém os registros armazenados como pares de valores-chave.

Índices

Os índices permitem consultar dados pelas propriedades dos objetos.

Tecnicamente, você cria índices em armazenamentos de objetos, que são chamados de armazenamentos de objetos pai.

Por exemplo, se você armazenar as informações de contato, poderá criar índices de e-mail, nome e sobrenome para poder consultar os contatos por meio dessas propriedades.

Conceitos básicos do IndexedDB

A seguir, apresentamos brevemente os conceitos básicos do IndexedDB:

1) Os bancos de dados IndexedDB armazenam pares de valores-chave

Ao contrário de localStorage e sessionStorage , os valores armazenados no IndexedDB podem ser estruturas complexas como objetos e blob.

Além disso, as chaves podem ser propriedades desses objetos ou podem ser objetos binários.

Para pesquisa e classificação rápidas, você pode criar índices que usam qualquer propriedade dos objetos.

2) IndexedDB é transacional

Cada leitura e gravação nos bancos de dados IndexedDB sempre acontece em uma transação.

O modelo transacional garante a integridade dos dados caso os usuários abram a aplicação web em duas abas/janelas ao mesmo tempo e realizem a leitura e gravação no mesmo banco de dados.

3) A API IndexedDB é principalmente assíncrona

As operações IndexedDB são assíncronas. Ele usa eventos DOM para notificá-lo quando uma operação for concluída e o resultado estiver disponível.

4) IndexedDB é um sistema NoSQL

O IndexedDB é um sistema NoSQL. Em outras palavras, não usa SQL para consultar dados. Em vez disso, utiliza a consulta que retorna um cursor. Então, você pode usar o cursor para iterar o conjunto de resultados.

5) IndexedDB segue a política de mesma origem

Uma origem é um domínio, protocolo e porta de uma URL do documento onde o código é executado. Por exemplo https://tutorials.acervolima.com:

  • domínio: tutorials.acervolima.com
  • protocolo: https
  • porta: 443

Os https://tutorials.acervolima.com/dom/e https://tutorials.acervolima.com/são da mesma origem porque possuem o mesmo domínio, protocolo e porta.

No entanto, https://tutorials.acervolima.com/e https://tutorials.acervolima.com/não são da mesma origem, pois possuem protocolos e portas diferentes:

https://tutorials.acervolima.com https://tutorials.acervolima.com
Protocolo https http
Porta 443 80

IndexedDB adere à política de mesma origem. Isso significa que cada origem possui seu próprio conjunto de bancos de dados. E uma origem não pode acessar bancos de dados de outras origens.

Operações básicas do IndexedDB

A seguir descrevemos as operações básicas nos bancos de dados IndexedDB, como

  • Abrindo uma conexão com um banco de dados.
  • Inserindo um objeto no armazenamento de objetos.
  • Lendo dados do armazenamento de objetos.
  • Usando um cursor para iterar sobre um conjunto de resultados.
  • Excluindo um objeto do armazenamento de objetos.

Antes de abrir uma conexão com um banco de dados no IndexedDB, vamos primeiro criar a estrutura do projeto.

1) Crie a estrutura do projeto

Primeiro, crie uma nova pasta chamada indexeddbpasta. Dentro da indexeddbpasta, crie outra subpasta chamada js.

Em segundo lugar, crie na index.htmlpasta indexeddb, app.jsna jspasta.

Terceiro, coloque a <script>tag vinculada ao app.jsarquivo no index.htmlarquivo assim:

<!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>Linguagem de código:  HTML, XML  ( xml )

No app.js, você colocará todo o código JavaScript em um IIFE .

(function () {
   // all the code will be here  
   // ...
})();Linguagem de código:  JavaScript  ( javascript )

1) Verifique se o IndexedDB é compatível

O código a seguir verifica se um navegador da web oferece suporte ao IndexedDB:

if (!window.indexedDB) {
    console.log(`Your browser doesn't support IndexedDB`);
    return;
}Linguagem de código:  JavaScript  ( javascript )

Como a maioria dos navegadores modernos suporta IndexedDB, isso pode não ser mais necessário.

2) Abra um banco de dados

Para abrir uma conexão com um banco de dados, você usa o open()método window.indexedDB:

const request = indexedDB.open('CRM', 1);Linguagem de código:  JavaScript  ( javascript )

O open()método aceita dois argumentos:

  • O nome do banco de dados ( CRM)
  • A versão do banco de dados ( 1)

O open()método retorna um objeto de solicitação que é uma instância da IDBOpenDBRequest interface.

Quando você chama o open()método, ele pode ter êxito ou falhar. Para lidar com cada caso, você pode atribuir o manipulador de eventos correspondente da seguinte forma:

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

request.onsuccess = (event) => {
    // add implementation here
};Linguagem de código:  JavaScript  ( javascript )

3) Crie armazenamentos de objetos

Ao abrir o banco de dados pela primeira vez, o onupgradeneededevento será acionado.

Se você abrir o banco de dados pela segunda vez com uma versão superior à versão existente, o onupgradeneeded  evento também será acionado.

Pela primeira vez, você pode usar o onupgradeneededmanipulador de eventos para inicializar os armazenamentos e índices de objetos.

Por exemplo, o onupgradeneededmanipulador de eventos a seguir cria o Contactsarmazenamento de objetos e seu índice.

 // 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
     });
 };Linguagem de código:  JavaScript  ( javascript )

Como funciona.

  • Primeiro, obtenha a IDBDatabase instância de event.target.resulte atribua-a à dbvariável.
  • Segundo, chame o createObjectStore()método para criar o Contactsarmazenamento de objetos com a autoincrementchave. Isso significa que o IndexedDB irá gerar um número de incremento automático começando em um como a chave para cada novo objeto inserido no Contactsarmazenamento de objetos.
  • Terceiro, chame o createIndex()método para criar um índice na emailpropriedade. Como o email é único, o índice também deve ser único. Para fazer isso, você especifica o terceiro argumento do createIndex()método { unique: true }.

4) Insira dados em armazenamentos de objetos

Depois de abrir uma conexão com o banco de dados com êxito, você poderá gerenciar os dados no onsuccessmanipulador de eventos.

Por exemplo, para adicionar um objeto a um armazenamento de objetos, siga estas etapas:

  • Primeiro, abra uma nova transação.
  • Segundo, obtenha um armazenamento de objetos.
  • Terceiro, chame o put()método de armazenamento de objetos para inserir um novo registro.
  • Por fim, feche a conexão com o banco de dados assim que a transação for concluída.

A insertContact()função a seguir insere um novo contato no Contactsarmazenamento de objetos:

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();
    };
}Linguagem de código:  JavaScript  ( javascript )

Para criar uma nova transação, você chama o transaction()método do IDBDatabaseobjeto.

Você pode abrir uma transação em um dos dois modos: readwriteou readonly. O readwritemodo permite ler e gravar dados no banco de dados, enquanto o readonlymodo permite apenas ler dados do banco de dados.

É uma boa prática abrir uma readonlytransação se você precisar ler apenas dados de um banco de dados.

Após definir a insertContact()função, você pode chamá-la no onsuccessmanipulador de eventos da requisição para inserir um ou mais contatos assim:

request.onsuccess = (event) => {
     const db = event.target.result;

     insertContact(db, {
         email: '[email protected]',
         firstName: 'John',
         lastName: 'Doe'
     });

     insertContact(db, {
         email: '[email protected]',
         firstName: 'Jane',
         lastName: 'Doe'
     });
};Linguagem de código:  JavaScript  ( javascript )

Agora, se você abrir o index.htmlarquivo no navegador da web, o código no app.js será executado para:

  • Crie o CRMbanco de dados no IndexedDB.
  • Crie o Contactsarmazenamento de objetos no CRMbanco de dados.
  • Insira dois registros no armazenamento de objetos.

Se você abrir o devtools no navegador da web, verá o banco de dados CRM com o Contactsarmazenamento de objetos. E no Contactsarmazenamento de objetos, você verá os dados conforme mostrado na imagem a seguir:

5) Leia os dados do armazenamento de objetos por chave

Para ler um objeto por sua chave, você usa o get()método de armazenamento de objetos. A getContactById()função a seguir encontra um contato por um ID:

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();
    };
};Linguagem de código:  JavaScript  ( javascript )

Quando você chama o método get() do armazenamento de objetos, ele retorna uma consulta que será executada de forma assíncrona.

Como a consulta pode ser bem-sucedida ou falhar, você precisa atribuir os manipuladores onsuccesse onerrorpara lidar com cada caso.

Se a consulta for bem-sucedida, você obterá o resultado em event.target.result. Caso contrário, você receberá um código de erro via event.target.errorCode.

O código a seguir fecha a conexão com o banco de dados assim que a transação for concluída:

txn.oncomplete = function () {
   db.close();
};Linguagem de código:  JavaScript  ( javascript )

Na verdade, a conexão com o banco de dados é fechada somente quando todas as transações são concluídas.

O seguinte chama o manipulador getContactById()de onsuccesseventos para obter o contato com id 1:

request.onsuccess = (event) => {
    const db = event.target.result;
    getContactById(db, 1);
};Linguagem de código:  JavaScript  ( javascript )

Saída:

6) Ler dados do armazenamento de objetos por um índice

O seguinte define uma nova função chamada getContactByEmail()que usa o índice de e-mail para consultar dados:

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();
    };
}Linguagem de código:  JavaScript  ( javascript )

Como funciona.

  • Primeiro, obtenha o objeto de índice de e-mail do Contactsarmazenamento de objetos.
  • Segundo, use o índice para ler os dados chamando o get()método.
  • Terceiro, mostre o resultado no onsuccessmanipulador de eventos da consulta.

O seguinte ilustra como usar a getContactByEmail()função no onsuccessmanipulador de eventos:

request.onsuccess = (event) => {
    const db = event.target.result;
    // get contact by email
    getContactByEmail(db, '[email protected]');
};Linguagem de código:  JavaScript  ( javascript )

Saída:

7) Leia todos os dados de um armazenamento de objetos

A seguir mostramos como usar um cursor para ler todos os objetos do Contactsarmazenamento de objetos:

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();
    };
}Linguagem de código:  JavaScript  ( javascript )

O objectStore.openCursor()retorna um cursor usado para iterar sobre um armazenamento de objetos.

Para iterar sobre os objetos em um armazenamento de objetos usando o cursor, você precisa atribuir um onsuccessmanipulador:

objectStore.openCursor().onsuccess = (event) => {
   //...
};
Linguagem de código:  JavaScript  ( javascript )

O event.target.resultretorna o cursor. Para obter os dados, você usa a cursor.valuepropriedade.

O cursor.continue()método avança o cursor para a posição do próximo registro no armazenamento de objetos.

O seguinte chama o manipulador getAllContacts()de onsuccesseventos para mostrar todos os dados do Contactsarmazenamento de objetos:

request.onsuccess = (event) => {
    const db = event.target.result;
    // get all contacts
    getAllContacts(db);
};Linguagem de código:  JavaScript  ( javascript )

Saída:

8) Exclua um contato

Para excluir um registro do armazenamento de objetos, você usa o delete()método de armazenamento de objetos.

A função a seguir exclui um contato pelo seu ID do Contactsarmazenamento de objetos:

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();
    };
}Linguagem de código:  JavaScript  ( javascript )

E você pode chamar a deleteContact()função no onsuccessmanipulador de eventos para excluir o contato com id 1 da seguinte maneira:

request.onsuccess = (event) => {
    const db = event.target.result;
    deleteContact(db, 1);
};Linguagem de código:  JavaScript  ( javascript )

Se você executar o código, verá que o contato com id 1 será excluído.

Junte tudo

O seguinte mostra o arquivo app.js completo:

(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: '[email protected]',
        //     firstName: 'John',
        //     lastName: 'Doe'
        // });

        // insertContact(db, {
        //     email: '[email protected]',
        //     firstName: 'Jane',
        //     lastName: 'Doe'
        // });


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


        // get contact by email
        // getContactByEmail(db, '[email protected]');

        // 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 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();
        };
    }


    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();
        };

    }
})();Linguagem de código:  JavaScript  ( javascript )

Resumo

  • O IndexedDB é um objeto de grande escala armazenado em navegadores da web.
  • O IndexedDB armazena dados como pares de valores-chave. Os valores podem ser quaisquer dados, incluindo dados simples e complexos.
  • O IndexedDB consiste em um ou mais bancos de dados. Cada banco de dados possui um ou mais armazenamentos de objetos. Normalmente, você cria um banco de dados no IndexedDB por aplicativo da web.
  • O IndexedDB é útil para aplicações web que não requerem uma conexão persistente com a Internet, especialmente para aplicações que funcionam tanto online quanto offline.

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *