Bekerja dengan IndexedDB

Panduan ini membahas dasar-dasar IndexedDB API. Kami menggunakan library IndexedDB Promised milik Jake Archibald, yang sangat mirip dengan IndexedDB API, tetapi menggunakan promise, yang dapat Anda await untuk sintaksis yang lebih ringkas. Tindakan ini menyederhanakan API sekaligus mempertahankan strukturnya.

Apa yang dimaksud dengan IndexedDB?

IndexedDB adalah sistem penyimpanan NoSQL berskala besar yang memungkinkan penyimpanan apa saja di browser pengguna. Selain tindakan pencarian, get, dan put seperti biasa, IndexedDB juga mendukung transaksi, dan sangat cocok untuk menyimpan data terstruktur dalam jumlah besar.

Setiap database IndexedDB bersifat unik untuk sebuah origin (biasanya domain situs atau subdomain), artinya database tersebut tidak dapat mengakses atau diakses oleh asal lain. Batas penyimpanan datanya biasanya besar, jika ada, tetapi browser yang berbeda menangani batas dan penghapusan data secara berbeda. Lihat bagian Bacaan lebih lanjut untuk informasi selengkapnya.

Persyaratan IndexedDB

Database
Nilai IndexedDB tertinggi. {i>Database<i} ini berisi penyimpanan objek, yang akan berisi data yang ingin Anda pertahankan. Anda dapat membuat beberapa database dengan nama apa pun yang Anda pilih.
Penyimpanan objek
Bucket individual untuk menyimpan data, serupa dengan tabel dalam database relasional. Biasanya, ada satu penyimpanan objek untuk setiap jenis (bukan jenis data JavaScript) data yang Anda simpan. Tidak seperti dalam tabel database, jenis data JavaScript di app store tidak harus konsisten. Misalnya, jika aplikasi memiliki penyimpanan objek people yang berisi informasi tentang tiga orang, properti usia orang-orang tersebut dapat berupa 53, 'twenty-five', dan unknown.
Indeks
Jenis penyimpanan objek untuk mengatur data di penyimpanan objek lain (disebut penyimpanan objek referensi) berdasarkan properti individual data. Indeks digunakan untuk mengambil catatan di penyimpanan objek oleh properti ini. Misalnya, jika Anda menyimpan orang, Anda mungkin ingin mengambilnya nanti berdasarkan nama, usia, atau hewan favorit mereka.
Operasi
Interaksi dengan database.
Transaksi
Wrapper di sekitar operasi atau grup operasi yang memastikan integritas database. Jika salah satu tindakan dalam transaksi gagal, tidak ada yang diterapkan dan database kembali ke status sebelum transaksi dimulai. Semua operasi baca atau tulis di IndexedDB harus menjadi bagian dari transaksi. Hal ini memungkinkan operasi baca-modifikasi-tulis atomik tanpa risiko konflik dengan thread lain yang bertindak pada database secara bersamaan.
Kursor
Mekanisme untuk melakukan iterasi pada beberapa record dalam database.

Cara memeriksa dukungan IndexedDB

IndexedDB hampir didukung secara universal. Namun, jika Anda menggunakan browser lama, sebaiknya jangan mendeteksi fitur untuk berjaga-jaga. Cara termudah adalah memeriksa objek window:

function indexedDBStuff () {
  // Check for IndexedDB support:
  if (!('indexedDB' in window)) {
    // Can't use IndexedDB
    console.log("This browser doesn't support IndexedDB");
    return;
  } else {
    // Do IndexedDB stuff here:
    // ...
  }
}

// Run IndexedDB code:
indexedDBStuff();

Cara membuka database

Dengan IndexedDB, Anda dapat membuat beberapa {i>database<i} dengan nama pilihan Anda. Jika tidak ada database saat Anda mencoba membukanya, database tersebut akan dibuat secara otomatis. Untuk membuka database, gunakan metode openDB() dari library idb:

import {openDB} from 'idb';

async function useDB () {
  // Returns a promise, which makes `idb` usable with async-await.
  const dbPromise = await openDB('example-database', version, events);
}

useDB();

Metode ini menampilkan promise yang di-resolve ke objek database. Saat menggunakan metode openDB(), berikan nama, nomor versi, dan objek peristiwa untuk menyiapkan database.

Berikut adalah contoh metode openDB() dalam konteks:

import {openDB} from 'idb';

async function useDB () {
  // Opens the first version of the 'test-db1' database.
  // If the database does not exist, it will be created.
  const dbPromise = await openDB('test-db1', 1);
}

useDB();

Tempatkan centang untuk dukungan IndexedDB di bagian atas fungsi anonim. Tindakan ini akan keluar dari fungsi jika browser tidak mendukung IndexedDB. Jika dapat dilanjutkan, fungsi tersebut akan memanggil metode openDB() untuk membuka database bernama 'test-db1'. Dalam contoh ini, objek peristiwa opsional telah diabaikan agar semuanya tetap sederhana, tetapi Anda harus menentukannya untuk melakukan pekerjaan yang bermakna dengan IndexedDB.

Cara bekerja dengan penyimpanan objek

Database IndexedDB berisi satu atau beberapa penyimpanan objek, yang masing-masing memiliki kolom untuk kunci, dan kolom lain untuk data yang terkait dengan kunci tersebut.

Membuat penyimpanan objek

Database IndexedDB yang terstruktur dengan baik harus memiliki satu penyimpanan objek untuk setiap jenis data yang perlu dipertahankan. Misalnya, situs yang mempertahankan profil dan catatan pengguna mungkin memiliki penyimpanan objek people yang berisi objek person, dan penyimpanan objek notes yang berisi objek note.

Untuk memastikan integritas database, Anda hanya dapat membuat atau menghapus penyimpanan objek dalam objek peristiwa dalam panggilan openDB(). Objek peristiwa mengekspos metode upgrade() yang memungkinkan Anda membuat penyimpanan objek. Panggil metode createObjectStore() di dalam metode upgrade() untuk membuat penyimpanan objek:

import {openDB} from 'idb';

async function createStoreInDB () {
  const dbPromise = await openDB('example-database', 1, {
    upgrade (db) {
      // Creates an object store:
      db.createObjectStore('storeName', options);
    }
  });
}

createStoreInDB();

Metode ini mengambil nama penyimpanan objek dan objek konfigurasi opsional yang memungkinkan Anda menentukan berbagai properti untuk penyimpanan objek.

Berikut adalah contoh cara menggunakan createObjectStore():

import {openDB} from 'idb';

async function createStoreInDB () {
  const dbPromise = await openDB('test-db1', 1, {
    upgrade (db) {
      console.log('Creating a new object store...');

      // Checks if the object store exists:
      if (!db.objectStoreNames.contains('people')) {
        // If the object store does not exist, create it:
        db.createObjectStore('people');
      }
    }
  });
}

createStoreInDB();

Dalam contoh ini, objek peristiwa diteruskan ke metode openDB() untuk membuat penyimpanan objek, dan seperti sebelumnya, pekerjaan membuat penyimpanan objek dilakukan dalam metode upgrade() objek peristiwa. Namun, karena browser menampilkan error jika Anda mencoba membuat penyimpanan objek yang sudah ada, sebaiknya gabungkan metode createObjectStore() dalam pernyataan if yang memeriksa apakah penyimpanan objek tersebut ada atau tidak. Di dalam blok if, panggil createObjectStore() untuk membuat penyimpanan objek bernama 'firstOS'.

Cara menentukan kunci utama

Saat menentukan penyimpanan objek, Anda dapat menentukan cara data diidentifikasi secara unik di penyimpanan menggunakan kunci utama. Anda dapat menentukan kunci utama dengan menentukan jalur kunci atau dengan menggunakan generator kunci.

Jalur kunci adalah properti yang selalu ada dan berisi nilai unik. Misalnya, dalam kasus penyimpanan objek people, Anda dapat memilih alamat email sebagai jalur kunci:

import {openDB} from 'idb';

async function createStoreInDB () {
  const dbPromise = await openDB('test-db2', 1, {
    upgrade (db) {
      if (!db.objectStoreNames.contains('people')) {
        db.createObjectStore('people', { keyPath: 'email' });
      }
    }
  });
}

createStoreInDB();

Contoh ini membuat penyimpanan objek yang disebut 'people' dan menetapkan properti email sebagai kunci utama dalam opsi keyPath.

Anda juga dapat menggunakan generator kunci seperti autoIncrement. Generator kunci membuat nilai unik untuk setiap objek yang ditambahkan ke penyimpanan objek. Secara default, jika Anda tidak menentukan kunci, IndexedDB akan membuat kunci dan menyimpannya secara terpisah dari data.

Contoh berikut membuat penyimpanan objek yang disebut 'notes' dan menetapkan kunci utama yang akan ditetapkan secara otomatis sebagai angka yang bertambah otomatis:

import {openDB} from 'idb';

async function createStoreInDB () {
  const dbPromise = await openDB('test-db2', 1, {
    upgrade (db) {
      if (!db.objectStoreNames.contains('notes')) {
        db.createObjectStore('notes', { autoIncrement: true });
      }
    }
  });
}

createStoreInDB();

Contoh berikut serupa dengan contoh sebelumnya, tetapi kali ini nilai yang bertambah otomatis secara eksplisit ditetapkan ke properti bernama 'id'.

import {openDB} from 'idb';

async function createStoreInDB () {
  const dbPromise = await openDB('test-db2', 1, {
    upgrade (db) {
      if (!db.objectStoreNames.contains('logs')) {
        db.createObjectStore('logs', { keyPath: 'id', autoIncrement: true });
      }
    }
  });
}

createStoreInDB();

Memilih metode yang akan digunakan untuk menentukan kunci bergantung pada data Anda. Jika data Anda memiliki properti yang selalu unik, Anda dapat menjadikannya keyPath untuk menerapkan keunikan ini. Jika tidak, gunakan nilai yang bertambah otomatis.

Kode berikut membuat tiga penyimpanan objek yang menunjukkan berbagai cara menentukan kunci utama di penyimpanan objek:

import {openDB} from 'idb';

async function createStoresInDB () {
  const dbPromise = await openDB('test-db2', 1, {
    upgrade (db) {
      if (!db.objectStoreNames.contains('people')) {
        db.createObjectStore('people', { keyPath: 'email' });
      }

      if (!db.objectStoreNames.contains('notes')) {
        db.createObjectStore('notes', { autoIncrement: true });
      }

      if (!db.objectStoreNames.contains('logs')) {
        db.createObjectStore('logs', { keyPath: 'id', autoIncrement: true });
      }
    }
  });
}

createStoresInDB();

Cara menentukan indeks

Indeks adalah jenis penyimpanan objek yang digunakan untuk mengambil data dari penyimpanan objek referensi berdasarkan properti tertentu. Indeks berada di dalam penyimpanan objek referensi dan berisi data yang sama, tetapi menggunakan properti yang ditentukan sebagai jalur kuncinya, bukan kunci utama penyimpanan referensi. Indeks harus dibuat saat Anda membuat penyimpanan objek, dan dapat digunakan untuk menentukan batasan unik pada data Anda.

Untuk membuat indeks, panggil metode createIndex() pada instance penyimpanan objek:

import {openDB} from 'idb';

async function createIndexInStore() {
  const dbPromise = await openDB('storeName', 1, {
    upgrade (db) {
      const objectStore = db.createObjectStore('storeName');

      objectStore.createIndex('indexName', 'property', options);
    }
  });
}

createIndexInStore();

Metode ini membuat dan menampilkan objek indeks. Metode createIndex() pada instance penyimpanan objek menggunakan nama indeks baru sebagai argumen pertama, dan argumen kedua mengacu pada properti pada data yang ingin Anda indeks. Argumen terakhir memungkinkan Anda menentukan dua opsi yang menentukan cara indeks beroperasi: unique dan multiEntry. Jika unique disetel ke true, indeks tidak mengizinkan nilai duplikat untuk kunci tunggal. Selanjutnya, multiEntry menentukan perilaku createIndex() saat properti yang diindeks adalah array. Jika ditetapkan ke true, createIndex() akan menambahkan entri dalam indeks untuk setiap elemen array. Jika tidak, kode ini akan menambahkan satu entri yang berisi array.

Berikut contohnya:

import {openDB} from 'idb';

async function createIndexesInStores () {
  const dbPromise = await openDB('test-db3', 1, {
    upgrade (db) {
      if (!db.objectStoreNames.contains('people')) {
        const peopleObjectStore = db.createObjectStore('people', { keyPath: 'email' });

        peopleObjectStore.createIndex('gender', 'gender', { unique: false });
        peopleObjectStore.createIndex('ssn', 'ssn', { unique: true });
      }

      if (!db.objectStoreNames.contains('notes')) {
        const notesObjectStore = db.createObjectStore('notes', { autoIncrement: true });

        notesObjectStore.createIndex('title', 'title', { unique: false });
      }

      if (!db.objectStoreNames.contains('logs')) {
        const logsObjectStore = db.createObjectStore('logs', { keyPath: 'id', autoIncrement: true });
      }
    }
  });
}

createIndexesInStores();

Dalam contoh ini, penyimpanan objek 'people' dan 'notes' memiliki indeks. Untuk membuat indeks, pertama-tama tetapkan hasil createObjectStore() (objek penyimpanan objek) ke variabel sehingga Anda dapat memanggil createIndex() pada variabel tersebut.

Cara bekerja dengan data

Bagian ini menjelaskan cara membuat, membaca, memperbarui, dan menghapus data. Semua operasi ini bersifat asinkron, menggunakan promise dengan IndexedDB API menggunakan permintaan. Hal ini menyederhanakan API. Daripada memproses peristiwa yang dipicu oleh permintaan tersebut, Anda dapat memanggil .then() pada objek database yang ditampilkan dari metode openDB() untuk memulai interaksi dengan database, atau await membuatnya.

Semua operasi data di IndexedDB dilakukan di dalam transaksi. Setiap operasi memiliki bentuk berikut:

  1. Mendapatkan objek database.
  2. Buka transaksi di database.
  3. Buka penyimpanan objek saat transaksi.
  4. Melakukan operasi pada penyimpanan objek.

Transaksi dapat dianggap sebagai wrapper yang aman di sekitar operasi atau sekelompok operasi. Jika salah satu tindakan dalam transaksi gagal, semua tindakan akan di-roll back. Transaksi bersifat khusus untuk satu atau beberapa penyimpanan objek, yang Anda tentukan saat membuka transaksi. Fitur ini dapat bersifat hanya-baca atau baca dan tulis. Ini menandakan apakah operasi di dalam transaksi membaca data atau membuat perubahan pada database.

Membuat data

Untuk membuat data, panggil metode add() pada instance database dan teruskan data yang ingin Anda tambahkan. Argumen pertama metode add() adalah penyimpanan objek yang ingin Anda tambahi data, dan argumen kedua adalah objek yang berisi kolom dan data terkait yang ingin Anda tambahkan. Berikut adalah contoh paling sederhana, dengan satu baris data ditambahkan:

import {openDB} from 'idb';

async function addItemToStore () {
  const db = await openDB('example-database', 1);

  await db.add('storeName', {
    field: 'data'
  });
}

addItemToStore();

Setiap panggilan add() terjadi dalam transaksi. Jadi, meskipun promise berhasil diselesaikan, bukan berarti operasi tersebut berhasil. Untuk memastikan operasi penambahan dilakukan, Anda harus memeriksa apakah seluruh transaksi telah selesai menggunakan metode transaction.done(). Ini adalah janji yang di-resolve saat transaksi selesai sendiri, dan menolak jika transaksi mengalami error. Anda harus melakukan pemeriksaan ini untuk semua operasi "tulis", karena itu satu-satunya cara Anda untuk mengetahui bahwa perubahan pada database telah benar-benar terjadi.

Kode berikut menunjukkan penggunaan metode add() di dalam transaksi:

import {openDB} from 'idb';

async function addItemsToStore () {
  const db = await openDB('test-db4', 1, {
    upgrade (db) {
      if (!db.objectStoreNames.contains('foods')) {
        db.createObjectStore('foods', { keyPath: 'name' });
      }
    }
  });
  
  // Create a transaction on the 'foods' store in read/write mode:
  const tx = db.transaction('foods', 'readwrite');

  // Add multiple items to the 'foods' store in a single transaction:
  await Promise.all([
    tx.store.add({
      name: 'Sandwich',
      price: 4.99,
      description: 'A very tasty sandwich!',
      created: new Date().getTime(),
    }),
    tx.store.add({
      name: 'Eggs',
      price: 2.99,
      description: 'Some nice eggs you can cook up!',
      created: new Date().getTime(),
    }),
    tx.done
  ]);
}

addItemsToStore();

Setelah membuka database (dan membuat penyimpanan objek jika diperlukan), Anda harus membuka transaksi dengan memanggil metode transaction() di dalamnya. Metode ini mengambil argumen untuk toko tempat Anda ingin bertransaksi, serta modenya. Dalam hal ini, kita ingin menulis ke app store sehingga contoh ini menentukan 'readwrite'.

Langkah selanjutnya adalah mulai menambahkan item ke toko sebagai bagian dari transaksi. Pada contoh sebelumnya, kita menangani tiga operasi di penyimpanan 'foods' yang masing-masing menampilkan promise:

  1. Menambahkan catatan untuk roti lapis yang lezat.
  2. Menambahkan data untuk beberapa telur.
  3. Menandatangani bahwa transaksi selesai (tx.done).

Karena semua tindakan ini berbasis promise, kita harus menunggu semuanya selesai. Meneruskan promise ini ke Promise.all adalah cara yang bagus dan ergonomis untuk menyelesaikan hal ini. Promise.all menerima array promise dan selesai jika semua promise yang diteruskan ke promise tersebut telah di-resolve.

Untuk dua kumpulan data yang ditambahkan, antarmuka store instance transaksi memanggil add() dan meneruskan data ke kumpulan data tersebut. Anda dapat melakukan await panggilan Promise.all sehingga selesai saat transaksi selesai.

Membaca data

Untuk membaca data, panggil metode get() pada instance database yang Anda ambil menggunakan metode openDB(). get() mengambil nama penyimpanan dan nilai kunci utama objek yang ingin Anda ambil. Berikut adalah contoh dasarnya:

import {openDB} from 'idb';

async function getItemFromStore () {
  const db = await openDB('example-database', 1);

  // Get a value from the object store by its primary key value:
  const value = await db.get('storeName', 'unique-primary-key-value');
}

getItemFromStore();

Seperti halnya add(), metode get() menampilkan promise, sehingga Anda dapat melakukan await pada promise, atau menggunakan callback .then() promise.

Contoh berikut menggunakan metode get() pada penyimpanan objek 'foods' database 'test-db4' untuk mendapatkan satu baris dengan kunci utama 'name':

import {openDB} from 'idb';

async function getItemFromStore () {
  const db = await openDB('test-db4', 1);
  const value = await db.get('foods', 'Sandwich');

  console.dir(value);
}

getItemFromStore();

Pengambilan satu baris dari database cukup mudah: buka database, lalu tentukan penyimpanan objek dan nilai kunci utama dari baris yang datanya ingin Anda dapatkan. Karena metode get() menampilkan promise, Anda dapat melakukan await padanya.

Memperbarui data

Untuk memperbarui data, panggil metode put() pada penyimpanan objek. Metode put() mirip dengan metode add() dan juga dapat digunakan sebagai pengganti add() untuk membuat data. Berikut ini contoh dasar penggunaan put() untuk memperbarui baris di penyimpanan objek menurut nilai kunci utamanya:

import {openDB} from 'idb';

async function updateItemInStore () {
  const db = await openDB('example-database', 1);

  // Update a value from in an object store with an inline key:
  await db.put('storeName', { inlineKeyName: 'newValue' });

  // Update a value from in an object store with an out-of-line key.
  // In this case, the out-of-line key value is 1, which is the
  // auto-incremented value.
  await db.put('otherStoreName', { field: 'value' }, 1);
}

updateItemInStore();

Seperti metode lain, metode ini menampilkan promise. Anda juga dapat menggunakan put() sebagai bagian dari transaksi. Berikut adalah contoh menggunakan toko 'foods' dari sebelumnya yang memperbarui harga sandwich dan telur:

import {openDB} from 'idb';

async function updateItemsInStore () {
  const db = await openDB('test-db4', 1);
  
  // Create a transaction on the 'foods' store in read/write mode:
  const tx = db.transaction('foods', 'readwrite');

  // Update multiple items in the 'foods' store in a single transaction:
  await Promise.all([
    tx.store.put({
      name: 'Sandwich',
      price: 5.99,
      description: 'A MORE tasty sandwich!',
      updated: new Date().getTime() // This creates a new field
    }),
    tx.store.put({
      name: 'Eggs',
      price: 3.99,
      description: 'Some even NICER eggs you can cook up!',
      updated: new Date().getTime() // This creates a new field
    }),
    tx.done
  ]);
}

updateItemsInStore();

Cara item diperbarui bergantung pada cara Anda menyetel kunci. Jika Anda menetapkan keyPath, setiap baris dalam penyimpanan objek akan dikaitkan dengan kunci inline. Contoh sebelumnya memperbarui baris berdasarkan kunci ini, dan saat Anda mengupdate baris dalam situasi ini, Anda harus menentukan kunci tersebut untuk mengupdate item yang sesuai di penyimpanan objek. Anda juga dapat membuat kunci out-of-line dengan menetapkan autoIncrement sebagai kunci utama.

Menghapus data

Untuk menghapus data, panggil metode delete() pada penyimpanan objek:

import {openDB} from 'idb';

async function deleteItemFromStore () {
  const db = await openDB('example-database', 1);

  // Delete a value 
  await db.delete('storeName', 'primary-key-value');
}

deleteItemFromStore();

Seperti add() dan put(), Anda dapat menggunakan ini sebagai bagian dari transaksi:

import {openDB} from 'idb';

async function deleteItemsFromStore () {
  const db = await openDB('test-db4', 1);
  
  // Create a transaction on the 'foods' store in read/write mode:
  const tx = db.transaction('foods', 'readwrite');

  // Delete multiple items from the 'foods' store in a single transaction:
  await Promise.all([
    tx.store.delete('Sandwich'),
    tx.store.delete('Eggs'),
    tx.done
  ]);
}

deleteItemsFromStore();

Struktur interaksi database sama dengan operasi lainnya. Jangan lupa untuk memeriksa apakah seluruh transaksi telah selesai dengan menyertakan metode tx.done dalam array yang Anda teruskan ke Promise.all.

Mendapatkan semua data

Sejauh ini Anda hanya mengambil objek dari toko satu per satu. Anda juga dapat mengambil semua data, atau subset, dari penyimpanan atau indeks objek menggunakan metode getAll() atau kursor.

Metode getAll()

Cara paling sederhana untuk mengambil semua data penyimpanan objek adalah dengan memanggil getAll() pada penyimpanan atau indeks objek, seperti ini:

import {openDB} from 'idb';

async function getAllItemsFromStore () {
  const db = await openDB('test-db4', 1);

  // Get all values from the designated object store:
  const allValues = await db.getAll('storeName');

  console.dir(allValues);
}

getAllItemsFromStore();

Metode ini menampilkan semua objek di penyimpanan objek, tanpa batasan apa pun. Ini adalah cara paling langsung untuk mendapatkan semua nilai dari penyimpanan objek, tetapi juga yang paling tidak fleksibel.

import {openDB} from 'idb';

async function getAllItemsFromStore () {
  const db = await openDB('test-db4', 1);

  // Get all values from the designated object store:
  const allValues = await db.getAll('foods');

  console.dir(allValues);
}

getAllItemsFromStore();

Contoh ini memanggil getAll() pada penyimpanan objek 'foods'. Tindakan ini akan menampilkan semua objek dari 'foods' yang diurutkan berdasarkan kunci utama.

Cara menggunakan kursor

Kursor adalah cara yang lebih fleksibel untuk mengambil beberapa objek. Kursor memilih setiap objek dalam penyimpanan atau indeks objek satu per satu, sehingga Anda dapat melakukan sesuatu dengan data saat dipilih. Kursor, seperti operasi database lainnya, bekerja dalam transaksi.

Untuk membuat kursor, panggil openCursor() pada penyimpanan objek sebagai bagian dari transaksi. Dengan menggunakan penyimpanan 'foods' dari contoh sebelumnya, berikut cara memajukan kursor melalui semua baris data di penyimpanan objek:

import {openDB} from 'idb';

async function getAllItemsFromStoreWithCursor () {
  const db = await openDB('test-db4', 1);
  const tx = await db.transaction('foods', 'readonly');

  // Open a cursor on the designated object store:
  let cursor = await tx.store.openCursor();

  // Iterate on the cursor, row by row:
  while (cursor) {
    // Show the data in the row at the current cursor position:
    console.log(cursor.key, cursor.value);

    // Advance the cursor to the next row:
    cursor = await cursor.continue();
  }
}

getAllItemsFromStoreWithCursor();

Dalam hal ini, transaksi dibuka dalam mode 'readonly', dan metode openCursor dipanggil. Di loop while berikutnya, baris di posisi kursor saat ini dapat membuat properti key dan value dibaca, dan Anda dapat beroperasi pada nilai-nilai tersebut dengan cara apa pun yang paling sesuai untuk aplikasi Anda. Jika sudah siap, Anda dapat memanggil metode continue() objek cursor untuk menuju ke baris berikutnya, dan loop while akan berakhir saat kursor mencapai akhir set data.

Menggunakan kursor dengan rentang dan indeks

Indeks memungkinkan Anda mengambil data di penyimpanan objek berdasarkan properti selain kunci utama. Anda dapat membuat indeks di properti mana pun, yang menjadi keyPath untuk indeks, menentukan rentang di properti tersebut, dan mendapatkan data dalam rentang menggunakan getAll() atau kursor.

Tentukan rentang Anda menggunakan objek IDBKeyRange dan salah satu metode berikut:

Metode upperBound() dan lowerBound() menentukan batas atas dan bawah rentang tersebut.

IDBKeyRange.lowerBound(indexKey);

Atau:

IDBKeyRange.upperBound(indexKey);

Masing-masing mengambil satu argumen: nilai keyPath indeks untuk item yang ingin Anda tentukan sebagai batas atas atau bawah.

Metode bound() menentukan batas atas dan bawah:

IDBKeyRange.bound(lowerIndexKey, upperIndexKey);

Rentang untuk fungsi ini bersifat inklusif secara default, yang berarti rentang tersebut menyertakan data yang ditentukan sebagai batas rentang. Untuk mengecualikan nilai tersebut, tentukan rentang sebagai eksklusif dengan meneruskan true sebagai argumen kedua untuk lowerBound() atau upperBound(), atau sebagai argumen ketiga dan keempat dari bound(), untuk batas bawah dan atas masing-masing.

Contoh berikutnya menggunakan indeks di properti 'price' di penyimpanan objek 'foods'. Kini penyimpanan juga memiliki formulir yang terlampir dengan dua input untuk batas atas dan bawah rentang. Gunakan kode berikut untuk menemukan makanan dengan harga di antara batas tersebut:

import {openDB} from 'idb';

async function searchItems (lower, upper) {
  if (!lower === '' && upper === '') {
    return;
  }

  let range;

  if (lower !== '' && upper !== '') {
    range = IDBKeyRange.bound(lower, upper);
  } else if (lower === '') {
    range = IDBKeyRange.upperBound(upper);
  } else {
    range = IDBKeyRange.lowerBound(lower);
  }

  const db = await openDB('test-db4', 1);
  const tx = await db.transaction('foods', 'readonly');
  const index = tx.store.index('price');

  // Open a cursor on the designated object store:
  let cursor = await index.openCursor(range);

  if (!cursor) {
    return;
  }

  // Iterate on the cursor, row by row:
  while (cursor) {
    // Show the data in the row at the current cursor position:
    console.log(cursor.key, cursor.value);

    // Advance the cursor to the next row:
    cursor = await cursor.continue();
  }
}

// Get items priced between one and four dollars:
searchItems(1.00, 4.00);

Kode contoh terlebih dahulu akan mendapatkan nilai untuk batas dan memeriksa apakah batas tersebut ada atau tidak. Blok kode berikutnya menentukan metode yang akan digunakan untuk membatasi rentang berdasarkan nilai tersebut. Dalam interaksi database, buka penyimpanan objek pada transaksi seperti biasa, lalu buka indeks 'price' pada penyimpanan objek. Indeks 'price' memungkinkan Anda menelusuri item berdasarkan harga.

Kode kemudian membuka kursor pada indeks dan meneruskan dalam rentang. Kursor akan menampilkan promise yang mewakili objek pertama dalam rentang, atau undefined jika tidak ada data dalam rentang tersebut. Metode cursor.continue() menampilkan kursor yang mewakili objek berikutnya, dan terus berlanjut melalui loop hingga Anda mencapai akhir rentang.

Pembuatan versi database

Saat memanggil metode openDB(), Anda dapat menentukan nomor versi database dalam parameter kedua. Dalam semua contoh dalam panduan ini, versi telah ditetapkan ke 1, tetapi database dapat diupgrade ke versi baru jika Anda perlu mengubahnya dengan cara tertentu. Jika versi yang ditentukan lebih tinggi daripada versi database yang ada, callback upgrade dalam objek peristiwa akan dieksekusi sehingga Anda dapat menambahkan penyimpanan dan indeks objek baru ke database.

Objek db di callback upgrade memiliki properti oldVersion khusus, yang menunjukkan nomor versi database yang dapat diakses browser. Anda dapat meneruskan nomor versi ini ke dalam pernyataan switch untuk mengeksekusi blok kode di dalam callback upgrade berdasarkan nomor versi database yang ada. Berikut contohnya:

import {openDB} from 'idb';

const db = await openDB('example-database', 2, {
  upgrade (db, oldVersion) {
    switch (oldVersion) {
      case 0:
        // Create first object store:
        db.createObjectStore('store', { keyPath: 'name' });

      case 1:
        // Get the original object store, and create an index on it:
        const tx = await db.transaction('store', 'readwrite');
        tx.store.createIndex('name', 'name');
    }
  }
});

Contoh ini menetapkan versi database terbaru ke 2. Saat kode ini pertama kali dieksekusi, database belum ada di browser, sehingga oldVersion adalah 0, dan pernyataan switch dimulai pada case 0. Dalam contoh ini, tindakan ini akan menambahkan penyimpanan objek 'store' ke database.

Poin utama: Dalam pernyataan switch, biasanya ada break setelah setiap blok case, tetapi ini sengaja tidak digunakan di sini. Dengan demikian, jika database yang ada tertinggal beberapa versi, atau jika database tersebut tidak ada, kode akan terus berlanjut melalui blok case lainnya hingga diupdate. Jadi, dalam contoh ini, browser terus mengeksekusi melalui case 1, membuat indeks name di penyimpanan objek store.

Untuk membuat indeks 'description' pada penyimpanan objek 'store', update nomor versi dan tambahkan blok case baru seperti berikut:

import {openDB} from 'idb';

const db = await openDB('example-database', 3, {
  upgrade (db, oldVersion) {
    switch (oldVersion) {
      case 0:
        // Create first object store:
        db.createObjectStore('store', { keyPath: 'name' });

      case 1:
        // Get the original object store, and create an index on it:
        const tx = await db.transaction('store', 'readwrite');
        tx.store.createIndex('name', 'name');

      case 2:
        const tx = await db.transaction('store', 'readwrite');
        tx.store.createIndex('description', 'description');
    }
  }
});

Jika database yang Anda buat di contoh sebelumnya masih ada di browser, saat database ini dieksekusi, oldVersion adalah 2. Browser akan melewati case 0 dan case 1, lalu mengeksekusi kode dalam case 2, yang membuat indeks description. Setelah itu, browser memiliki database di versi 3 yang berisi penyimpanan objek store dengan indeks name dan description.

Bacaan lebih lanjut

Sumber daya berikut menyediakan informasi dan konteks selengkapnya untuk menggunakan IndexedDB.

Dokumentasi IndexedDB

Batas penyimpanan data