ใช้งาน IndexedDB

คู่มือนี้ครอบคลุมพื้นฐานของ IndexedDB API เราใช้ไลบรารี IndexedDB Prommediate ของ Jake Archibald ซึ่งคล้ายกับ IndexedDB API อย่างมาก แต่ใช้คำสัญญา ซึ่งคุณสามารถ await ได้เพื่อให้ไวยากรณ์กระชับมากขึ้น วิธีนี้ทำให้ API ง่ายขึ้นในขณะที่ บำรุงรักษาโครงสร้าง

IndexedDB คืออะไร

IndexedDB เป็นระบบพื้นที่เก็บข้อมูล NoSQL ขนาดใหญ่ที่ช่วยให้สามารถเก็บข้อมูลในเบราว์เซอร์ของผู้ใช้ได้เกือบทุกอย่าง นอกเหนือจากการค้นหา รับ และส่งข้อมูลตามปกติแล้ว IndexedDB ยังรองรับธุรกรรมด้วย และเหมาะอย่างยิ่งสำหรับการจัดเก็บ Structured Data จำนวนมาก

ฐานข้อมูล IndexedDB แต่ละรายการจะไม่ซ้ำกันสำหรับต้นทาง (โดยทั่วไปจะเป็นโดเมนเว็บไซต์หรือโดเมนย่อย) ซึ่งหมายความว่าไม่สามารถเข้าถึงหรือเข้าถึงจากต้นทางอื่นๆ ได้ ขีดจำกัดของพื้นที่เก็บข้อมูลมักมีขนาดใหญ่อยู่ หากมี แต่เบราว์เซอร์ที่แตกต่างกันจะจัดการขีดจำกัดและการขับไล่ข้อมูลแตกต่างกัน ดูข้อมูลเพิ่มเติมได้ที่ส่วนอ่านเพิ่มเติม

คำศัพท์ IndexedDB

ฐานข้อมูล
ระดับสูงสุดของ IndexedDB ซึ่งมีที่เก็บออบเจ็กต์ซึ่งกลับมีข้อมูลที่คุณต้องการคงไว้ คุณสามารถสร้างฐานข้อมูลได้หลายฐานข้อมูล โดยใช้ชื่อใดก็ได้ตามต้องการ
Object Store
ที่เก็บข้อมูลแต่ละรายการสำหรับจัดเก็บข้อมูล ซึ่งคล้ายกับตารางในฐานข้อมูลเชิงสัมพันธ์ โดยปกติแล้ว จะเก็บข้อมูลออบเจ็กต์ 1 รายการสำหรับแต่ละประเภท (ไม่ใช่ประเภทข้อมูล JavaScript) ที่คุณจัดเก็บ ประเภทข้อมูล JavaScript ในร้านค้าไม่จำเป็นต้องสอดคล้องกันและต่างจากตารางฐานข้อมูล เช่น หากแอปมีที่เก็บออบเจ็กต์ people ที่มีข้อมูลเกี่ยวกับ 3 คน พร็อพเพอร์ตี้อายุของผู้ใช้เหล่านั้นอาจเป็น 53, 'twenty-five' และ unknown
ดัชนี
ที่เก็บออบเจ็กต์ประเภทหนึ่งเพื่อจัดระเบียบข้อมูลในที่เก็บออบเจ็กต์อื่น (เรียกว่าการจัดเก็บออบเจ็กต์อ้างอิง) ตามพร็อพเพอร์ตี้แต่ละรายการของข้อมูล ดัชนีนี้จะใช้เพื่อเรียกข้อมูลระเบียนในที่เก็บออบเจ็กต์ของพร็อพเพอร์ตี้นี้ เช่น หากจะเก็บคนไว้ คุณอาจต้องการดึงข้อมูลในภายหลังตามชื่อ อายุ หรือสัตว์ที่ชอบ
การดำเนินการ
การโต้ตอบกับฐานข้อมูล
ธุรกรรม
Wrapper ของการดำเนินการหรือกลุ่มการดำเนินการที่จะรับประกันความสมบูรณ์ของฐานข้อมูล หากการดำเนินการอย่างใดอย่างหนึ่งในธุรกรรมล้มเหลว การดำเนินการอย่างใดอย่างหนึ่งจะไม่มีผล และฐานข้อมูลจะกลับสู่สถานะก่อนที่จะเริ่มต้นธุรกรรม การดำเนินการอ่านหรือเขียนทั้งหมดใน IndexedDB ต้องเป็นส่วนหนึ่งของธุรกรรม วิธีนี้ช่วยให้ใช้งานแบบอ่าน-แก้ไข-เขียนแบบอะตอมได้โดยไม่มีความเสี่ยงที่จะขัดแย้งกับเทรดอื่นๆ ที่ดำเนินการกับฐานข้อมูลในเวลาเดียวกัน
เคอร์เซอร์
กลไกสำหรับการทำซ้ำในหลายระเบียนในฐานข้อมูล

วิธีตรวจสอบการสนับสนุน IndexedDB

IndexedDB เกือบจะได้รับการรองรับแล้ว อย่างไรก็ตาม หากคุณกำลังใช้เบราว์เซอร์รุ่นเก่า ก็ไม่ควรตรวจหาการสนับสนุนเผื่อไว้ก่อน วิธีที่ง่ายที่สุดคือการตรวจสอบออบเจ็กต์ 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();

วิธีเปิดฐานข้อมูล

คุณสามารถสร้างฐานข้อมูลหลายรายการโดยใช้ชื่อที่คุณเลือกได้ด้วย IndexedDB หากพยายามเปิดฐานข้อมูลไม่ได้ ระบบจะสร้างฐานข้อมูลขึ้นมาโดยอัตโนมัติ หากต้องการเปิดฐานข้อมูล ให้ใช้เมธอด openDB() จากไลบรารี 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();

เมธอดนี้จะแสดงคำสัญญาที่แปลงไปเป็นออบเจ็กต์ฐานข้อมูล เมื่อใช้เมธอด openDB() ให้ระบุชื่อ หมายเลขเวอร์ชัน และออบเจ็กต์เหตุการณ์เพื่อตั้งค่าฐานข้อมูล

ต่อไปนี้คือตัวอย่างของเมธอด openDB() ในบริบท

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

วางการตรวจหาการสนับสนุน IndexedDB ที่ด้านบนของฟังก์ชันที่ไม่ระบุชื่อ การดำเนินการนี้จะปิดฟังก์ชันหากเบราว์เซอร์ไม่รองรับ IndexedDB หากฟังก์ชันยังดำเนินการต่อได้ ระบบจะเรียกใช้เมธอด openDB() เพื่อเปิดฐานข้อมูลชื่อ 'test-db1' ในตัวอย่างนี้ เราได้ปล่อยออบเจ็กต์เหตุการณ์ที่ไม่บังคับไว้เพื่อให้ทุกอย่างเรียบง่าย แต่คุณต้องระบุว่าออบเจ็กต์ดังกล่าวจะทำงานที่มีความหมายด้วย IndexedDB

วิธีทํางานกับ Object Store

ฐานข้อมูล IndexedDB มีที่เก็บออบเจ็กต์อย่างน้อย 1 รายการ ซึ่งแต่ละรายการจะมีคอลัมน์สำหรับคีย์ และอีกคอลัมน์สำหรับข้อมูลที่เชื่อมโยงกับคีย์นั้น

สร้างที่เก็บออบเจ็กต์

ฐานข้อมูล IndexedDB ที่มีโครงสร้างที่ดีควรมีที่เก็บออบเจ็กต์ 1 รายการสำหรับข้อมูลแต่ละประเภทที่ต้องคงไว้ ตัวอย่างเช่น เว็บไซต์ที่คงโปรไฟล์ผู้ใช้และหมายเหตุไว้อาจมีที่เก็บออบเจ็กต์ people ที่มีออบเจ็กต์ person และที่เก็บออบเจ็กต์ notes ที่มีออบเจ็กต์ note รายการ

เพื่อรับประกันความสมบูรณ์ของฐานข้อมูล คุณจะสร้างหรือนำที่เก็บออบเจ็กต์ออกได้ในออบเจ็กต์เหตุการณ์ในการเรียกใช้ openDB() เท่านั้น ออบเจ็กต์เหตุการณ์แสดงเมธอด upgrade() ที่ให้คุณสร้างที่เก็บออบเจ็กต์ได้ เรียกใช้เมธอด createObjectStore() ภายในเมธอด upgrade() เพื่อสร้างที่เก็บออบเจ็กต์ ดังนี้

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

เมธอดนี้ใช้ชื่อของที่เก็บออบเจ็กต์และออบเจ็กต์การกำหนดค่าที่ไม่บังคับ ซึ่งทำให้คุณกำหนดพร็อพเพอร์ตี้ต่างๆ สำหรับที่เก็บออบเจ็กต์ได้

ตัวอย่างวิธีใช้ 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();

ในตัวอย่างนี้ ระบบจะส่งออบเจ็กต์เหตุการณ์ไปยังเมธอด openDB() เพื่อสร้างที่เก็บออบเจ็กต์ และเช่นเคย การสร้างที่เก็บออบเจ็กต์จะดำเนินการในเมธอด upgrade() ของออบเจ็กต์เหตุการณ์ อย่างไรก็ตาม เนื่องจากเบราว์เซอร์จะแสดงข้อผิดพลาดหากคุณพยายามสร้างที่เก็บออบเจ็กต์ที่มีอยู่แล้ว เราขอแนะนำให้รวมเมธอด createObjectStore() ในคำสั่ง if ที่ตรวจสอบว่ามีที่เก็บออบเจ็กต์อยู่หรือไม่ ในบล็อก if ให้เรียกใช้ createObjectStore() เพื่อสร้างที่เก็บออบเจ็กต์ชื่อ 'firstOS'

วิธีกำหนดคีย์หลัก

เมื่อกำหนดที่เก็บออบเจ็กต์ คุณจะกำหนดวิธีระบุข้อมูลที่ไม่ซ้ำกันใน Store ได้โดยใช้คีย์หลัก คุณสามารถระบุคีย์หลักได้โดยการกำหนดเส้นทางคีย์หรือใช้เครื่องมือสร้างคีย์

เส้นทางคีย์คือพร็อพเพอร์ตี้ที่มีอยู่เสมอและมีค่าที่ไม่ซ้ำกัน ตัวอย่างเช่น ในกรณีของที่เก็บออบเจ็กต์ people คุณอาจเลือกอีเมลเป็นเส้นทางคีย์ ดังนี้

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

ตัวอย่างนี้สร้างที่เก็บออบเจ็กต์ชื่อ 'people' และกำหนดพร็อพเพอร์ตี้ email เป็นคีย์หลักในตัวเลือก keyPath

หรือจะใช้ตัวสร้างคีย์ เช่น autoIncrement ก็ได้ เครื่องมือสร้างคีย์จะสร้างค่าที่ไม่ซ้ำกันสำหรับทุกๆ ออบเจ็กต์ที่เพิ่มลงในที่เก็บออบเจ็กต์ โดยค่าเริ่มต้น หากคุณไม่ระบุคีย์ IndexedDB จะสร้างคีย์และจัดเก็บแยกจากข้อมูล

ตัวอย่างต่อไปนี้สร้างที่เก็บออบเจ็กต์ชื่อ 'notes' และกำหนดคีย์หลักให้เป็นตัวเลขที่เพิ่มขึ้นโดยอัตโนมัติ

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

ตัวอย่างต่อไปนี้คล้ายกับตัวอย่างก่อนหน้านี้ แต่คราวนี้มีการกำหนดค่าที่เพิ่มขึ้นอัตโนมัติให้กับพร็อพเพอร์ตี้ชื่อ '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();

การเลือกวิธีที่จะใช้กำหนดคีย์จะขึ้นอยู่กับข้อมูลของคุณ หากข้อมูลมีพร็อพเพอร์ตี้ที่ไม่ซ้ำกันเสมอ คุณอาจทำให้เป็น keyPath เพื่อบังคับใช้ความไม่ซ้ำกันนี้ได้ หรือใช้ค่าที่เพิ่มขึ้นโดยอัตโนมัติ

โค้ดต่อไปนี้จะสร้าง Object Store 3 รายการที่แสดงถึงวิธีต่างๆ ในการกำหนดคีย์หลักใน Object Store

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

วิธีกำหนดดัชนี

ดัชนีเป็นที่เก็บออบเจ็กต์ประเภทหนึ่งที่ใช้เพื่อดึงข้อมูลจากที่เก็บออบเจ็กต์การอ้างอิงตามพร็อพเพอร์ตี้ที่ระบุ ดัชนีจะอยู่ในที่เก็บออบเจ็กต์การอ้างอิงและมีข้อมูลเดียวกัน แต่ใช้พร็อพเพอร์ตี้ที่ระบุเป็นเส้นทางคีย์แทนคีย์หลักของที่เก็บอ้างอิง ระบบจะสร้างดัชนีเมื่อคุณสร้างที่เก็บออบเจ็กต์ และอาจใช้เพื่อกำหนดข้อจำกัดที่ไม่ซ้ำกันของข้อมูลได้

หากต้องการสร้างดัชนี ให้เรียกใช้เมธอด createIndex() ในอินสแตนซ์ของที่เก็บออบเจ็กต์ ดังนี้

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

วิธีนี้จะสร้างและแสดงผลออบเจ็กต์ดัชนี เมธอด createIndex() ในอินสแตนซ์ของที่เก็บออบเจ็กต์จะใช้ชื่อของดัชนีใหม่เป็นอาร์กิวเมนต์แรก และอาร์กิวเมนต์ที่ 2 หมายถึงพร็อพเพอร์ตี้ในข้อมูลที่คุณต้องการจัดทำดัชนี อาร์กิวเมนต์สุดท้ายให้คุณกำหนด 2 ตัวเลือกที่กำหนดวิธีการทำงานของดัชนี ได้แก่ unique และ multiEntry หากตั้งค่า unique เป็น true ดัชนีจะไม่อนุญาตให้ใส่ค่าที่ซ้ำกันสำหรับคีย์เดียว ถัดไป multiEntry จะกำหนดลักษณะการทำงานของ createIndex() เมื่อพร็อพเพอร์ตี้ที่จัดทำดัชนีเป็นอาร์เรย์ หากตั้งค่าเป็น true แล้ว createIndex() จะเพิ่มรายการในดัชนีสำหรับองค์ประกอบอาร์เรย์แต่ละรายการ มิเช่นนั้น ระบบจะเพิ่มรายการเดียวที่มีอาร์เรย์

ตัวอย่าง

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

ในตัวอย่างนี้ ร้านออบเจ็กต์ 'people' และ 'notes' มีดัชนี หากต้องการสร้างดัชนี ให้กำหนดผลลัพธ์ของ createObjectStore() (ออบเจ็กต์ร้านค้าออบเจ็กต์) ให้กับตัวแปรก่อนเพื่อให้คุณเรียกใช้ createIndex() กับตัวแปรได้

วิธีทำงานกับข้อมูล

ส่วนนี้จะอธิบายวิธีการสร้าง อ่าน อัปเดต และลบข้อมูล การดำเนินการเหล่านี้เป็นแบบอะซิงโครนัสทั้งหมด โดยมีการสัญญาว่า IndexedDB API ใช้คำขอที่ใดบ้าง ซึ่งจะช่วยให้ API ง่ายขึ้น แทนที่จะรอฟังเหตุการณ์ที่เกิดจากคำขอ คุณสามารถเรียกใช้ .then() ในออบเจ็กต์ฐานข้อมูลที่แสดงผลจากเมธอด openDB() เพื่อเริ่มการโต้ตอบกับฐานข้อมูลหรือawaitการสร้างฐานข้อมูลได้

การดำเนินการข้อมูลทั้งหมดใน IndexedDB จะดำเนินการภายในธุรกรรม โดยแต่ละการดำเนินการจะมีรูปแบบดังนี้

  1. รับออบเจ็กต์ฐานข้อมูล
  2. เปิดธุรกรรมบนฐานข้อมูล
  3. เปิด Object Store เมื่อทำธุรกรรม
  4. ดำเนินการใน Object Store

ธุรกรรมอาจถือเป็น Wrapper ที่ปลอดภัยสำหรับการดำเนินการหรือกลุ่มการดำเนินการ หากการดำเนินการอย่างใดอย่างหนึ่งภายในธุรกรรมล้มเหลว การดำเนินการทั้งหมดจะย้อนกลับ ธุรกรรมจะเฉพาะเจาะจงกับร้านค้าออบเจ็กต์อย่างน้อย 1 ร้าน ซึ่งคุณกำหนดเองเมื่อเปิดธุรกรรม ซึ่งจะเป็นแบบอ่านอย่างเดียว หรืออ่านและเขียนก็ได้ ซึ่งหมายความว่าการดำเนินการภายในธุรกรรมจะอ่านข้อมูลหรือทำการเปลี่ยนแปลงฐานข้อมูล

สร้างข้อมูล

หากต้องการสร้างข้อมูล ให้เรียกใช้เมธอด add() บนอินสแตนซ์ฐานข้อมูล แล้วส่งข้อมูลที่ต้องการเพิ่ม อาร์กิวเมนต์แรกของเมธอด add() คือที่เก็บออบเจ็กต์ที่คุณต้องการเพิ่มข้อมูล และอาร์กิวเมนต์ที่ 2 คือออบเจ็กต์ที่มีช่องและข้อมูลที่เกี่ยวข้องที่คุณต้องการเพิ่ม ต่อไปนี้เป็นตัวอย่างที่ง่ายที่สุดที่มีการเพิ่มข้อมูลแถวเดียว

import {openDB} from 'idb';

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

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

addItemToStore();

การเรียก add() แต่ละครั้งจะเกิดขึ้นภายในธุรกรรม ดังนั้นแม้ว่าสัญญาจะสำเร็จลุล่วงสำเร็จ ก็ไม่ได้หมายความว่าการดำเนินการจะได้ผลเสมอไป หากต้องการตรวจสอบว่าเพิ่มการดำเนินการสำเร็จแล้ว คุณต้องตรวจสอบว่าธุรกรรมทั้งหมดเสร็จสมบูรณ์หรือไม่โดยใช้เมธอด transaction.done() ธุรกรรมนี้จะอยู่ในสัญญาว่าจะแก้ไขเมื่อธุรกรรมเสร็จสมบูรณ์เอง และจะปฏิเสธธุรกรรมหากเกิดข้อผิดพลาด คุณต้องทำการตรวจสอบนี้สำหรับการดำเนินการ "เขียน" ทั้งหมดเพราะเป็นวิธีเดียวที่จะรู้ว่ามีการเปลี่ยนแปลงฐานข้อมูลเกิดขึ้นจริง

รหัสต่อไปนี้แสดงการใช้เมธอด add() ภายในธุรกรรม

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

เมื่อเปิดฐานข้อมูล (และสร้าง Object Store หากจำเป็น) คุณจะต้องเปิดธุรกรรมโดยเรียกใช้เมธอด transaction() ในฐานข้อมูล เมธอดนี้ใช้อาร์กิวเมนต์สำหรับร้านค้าที่คุณต้องการทำธุรกรรม รวมถึงโหมดด้วย ในกรณีนี้เราสนใจที่จะเขียนถึง Store ตัวอย่างนี้จึงต้องระบุ 'readwrite'

ขั้นตอนถัดไปคือการเริ่มเพิ่มรายการลงใน Store โดยเป็นส่วนหนึ่งของธุรกรรม ในตัวอย่างก่อนหน้านี้ เราจะดำเนินการกับ 3 การดำเนินการในร้านค้า 'foods' ซึ่งแต่ละรายการให้ผลลัพธ์ตามคำสัญญา

  1. เพิ่มสถิติสำหรับแซนด์วิชแสนอร่อย
  2. กำลังเพิ่มระเบียนสำหรับไข่
  3. การส่งสัญญาณว่าธุรกรรมเสร็จสมบูรณ์แล้ว (tx.done)

เนื่องจากการดำเนินการทั้งหมดนี้เป็นไปตามคำสัญญา เราจึงต้องรอให้การดำเนินการทั้งหมดเสร็จสิ้น การทำตามสัญญาเหล่านี้ให้กับ Promise.all เป็นวิธีที่ดีและเป็นไปตามหลักการยศาสตร์ Promise.all ยอมรับคำมั่นสัญญาต่างๆ และดำเนินการให้เสร็จสิ้นเมื่อคำสัญญาทั้งหมดที่ส่งมาแก้ไขแล้ว

สำหรับระเบียน 2 รายการที่เพิ่มเข้ามา อินเทอร์เฟซ store ของอินสแตนซ์ธุรกรรมจะเรียกใช้ add() และส่งข้อมูลไปยังระเบียนดังกล่าว คุณสามารถ await เรียกใช้ Promise.all เพื่อดําเนินการจนเสร็จสิ้นเมื่อธุรกรรมเสร็จสมบูรณ์

อ่านข้อมูล

หากต้องการอ่านข้อมูล ให้เรียกใช้เมธอด get() ในอินสแตนซ์ฐานข้อมูลที่คุณดึงข้อมูลโดยใช้เมธอด openDB() get() ใช้ชื่อของ Store และค่าคีย์หลักของออบเจ็กต์ที่ต้องการเรียกข้อมูล ตัวอย่างเบื้องต้นมีดังนี้

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

เช่นเดียวกับ add() เมธอด get() จะแสดงคำสัญญาเพื่อให้คุณawaitได้หากต้องการ หรือใช้โค้ดเรียกกลับ .then() ของสัญญา

ตัวอย่างต่อไปนี้ใช้เมธอด get() ในที่เก็บออบเจ็กต์ 'foods' ของฐานข้อมูล 'test-db4' เพื่อรับแถวเดียวโดยคีย์หลัก '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();

การดึงข้อมูลแถวเดียวจากฐานข้อมูลค่อนข้างตรงไปตรงมา โดยเปิดฐานข้อมูลและระบุที่เก็บออบเจ็กต์และค่าคีย์หลักของแถวที่ต้องการรับข้อมูล เนื่องจากเมธอด get() ให้ผลลัพธ์สัญญา คุณจึงสามารถawaitได้

อัปเดตข้อมูล

หากต้องการอัปเดตข้อมูล ให้เรียกใช้เมธอด put() ในที่เก็บออบเจ็กต์ เมธอด put() คล้ายกับเมธอด add() และยังใช้แทน add() เพื่อสร้างข้อมูลได้อีกด้วย ตัวอย่างพื้นฐานของการใช้ put() เพื่ออัปเดตแถวในที่เก็บออบเจ็กต์ตามค่าคีย์หลักของแถวมีดังนี้

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

เมธอดนี้จะส่งกลับคำสัญญา เช่นเดียวกับวิธีอื่นๆ คุณยังใช้ put() เป็นส่วนหนึ่งของธุรกรรมได้ด้วย นี่คือตัวอย่างการใช้ร้าน 'foods' ก่อนหน้านี้ ซึ่งอัปเดตราคาแซนด์วิชและไข่

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

วิธีอัปเดตรายการจะขึ้นอยู่กับวิธีตั้งค่าคีย์ หากคุณตั้งค่า keyPath แต่ละแถวใน Object Store จะเชื่อมโยงกับคีย์ในบรรทัด ตัวอย่างก่อนหน้านี้จะอัปเดตแถวตามคีย์นี้ และเมื่อคุณอัปเดตแถวในสถานการณ์นี้ คุณจะต้องระบุคีย์นั้นเพื่ออัปเดตรายการที่เหมาะสมในที่เก็บออบเจ็กต์ นอกจากนี้ คุณยังสร้างคีย์แบบไม่อยู่ในบรรทัดได้ด้วยการตั้งค่า autoIncrement เป็นคีย์หลัก

ลบข้อมูล

หากต้องการลบข้อมูล ให้เรียกใช้เมธอด delete() ในที่เก็บออบเจ็กต์ ดังนี้

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

คุณสามารถใช้ URL นี้เป็นส่วนหนึ่งของธุรกรรม เช่น add() และ put()

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

โครงสร้างของการโต้ตอบในฐานข้อมูลจะเหมือนกับของการดำเนินการอื่นๆ อย่าลืมตรวจสอบว่าธุรกรรมทั้งหมดเสร็จสมบูรณ์แล้วโดยใส่เมธอด tx.done ในอาร์เรย์ที่คุณส่งไปยัง Promise.all

กำลังดูข้อมูลทั้งหมด

จนถึงตอนนี้คุณเรียกข้อมูลวัตถุจากร้านค้าได้ครั้งละ 1 รายการเท่านั้น นอกจากนี้ คุณยังดึงข้อมูลทั้งหมดหรือบางส่วนจากที่เก็บออบเจ็กต์หรือดัชนีโดยใช้เมธอด getAll() หรือเคอร์เซอร์ได้ด้วย

เมธอด getAll()

วิธีที่ง่ายที่สุดในการเรียกข้อมูลทั้งหมดของที่เก็บออบเจ็กต์คือการเรียกใช้ getAll() ในที่เก็บออบเจ็กต์หรือดัชนี ดังต่อไปนี้

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

เมธอดนี้จะแสดงผลออบเจ็กต์ทั้งหมดในที่เก็บออบเจ็กต์โดยไม่มีข้อจำกัดใดๆ ซึ่งเป็นวิธีที่ตรงที่สุดในการดึงค่าทั้งหมดจากที่เก็บออบเจ็กต์ แต่ก็มีความยืดหยุ่นน้อยที่สุดเช่นกัน

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

ตัวอย่างนี้เรียกใช้ getAll() ในที่เก็บออบเจ็กต์ 'foods' การดำเนินการนี้จะแสดงออบเจ็กต์ทั้งหมดจาก 'foods' ซึ่งเรียงลำดับตามคีย์หลัก

วิธีใช้เคอร์เซอร์

เคอร์เซอร์เป็นวิธีที่ยืดหยุ่นกว่าในการเรียกออบเจ็กต์หลายรายการ เคอร์เซอร์จะเลือกแต่ละออบเจ็กต์ในที่เก็บออบเจ็กต์หรือจัดทำดัชนีทีละรายการ เพื่อให้คุณดำเนินการบางอย่างกับข้อมูลได้เมื่อเลือกออบเจ็กต์นั้น เคอร์เซอร์ทำงานในธุรกรรม เช่นเดียวกับการดำเนินการอื่นๆ ของฐานข้อมูล

หากต้องการสร้างเคอร์เซอร์ ให้เรียกใช้ openCursor() ในออบเจ็กต์ Store ซึ่งเป็นส่วนหนึ่งของธุรกรรม หากใช้การจัดเก็บ 'foods' จากตัวอย่างก่อนหน้านี้ วิธีเลื่อนเคอร์เซอร์ไปยังแถวข้อมูลทั้งหมดในที่เก็บออบเจ็กต์

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

ธุรกรรมในกรณีนี้จะเปิดในโหมด 'readonly' และจะมีการเรียกใช้เมธอด openCursor ในการวนซ้ำ while ที่ตามมา แถวที่ตำแหน่งปัจจุบันของเคอร์เซอร์อาจอ่านพร็อพเพอร์ตี้ key และ value และคุณจะดำเนินการกับค่าเหล่านั้นในลักษณะที่เหมาะกับแอปที่สุดได้ เมื่อพร้อม ให้เรียกใช้เมธอด continue() ของออบเจ็กต์ cursor เพื่อไปยังแถวถัดไป จากนั้นลูป while จะยุติเมื่อเคอร์เซอร์มาถึงจุดสิ้นสุดของชุดข้อมูล

ใช้เคอร์เซอร์กับช่วงและดัชนี

ดัชนีช่วยให้คุณดึงข้อมูลในที่เก็บออบเจ็กต์ตามพร็อพเพอร์ตี้อื่นนอกเหนือจากคีย์หลักได้ คุณสร้างดัชนีในพร็อพเพอร์ตี้ใดก็ได้ซึ่งจะกลายเป็น keyPath สำหรับดัชนี ระบุช่วงในพร็อพเพอร์ตี้นั้น และรับข้อมูลภายในช่วงโดยใช้ getAll() หรือเคอร์เซอร์

กำหนดช่วงของคุณโดยใช้ออบเจ็กต์ IDBKeyRange และวิธีการใดก็ได้ต่อไปนี้

เมธอด upperBound() และ lowerBound() จะระบุขีดจำกัดสูงสุดและต่ำสุดของช่วง

IDBKeyRange.lowerBound(indexKey);

หรือ

IDBKeyRange.upperBound(indexKey);

แต่ละรายการจะใช้อาร์กิวเมนต์ 1 รายการ ซึ่งเป็นค่า keyPath ของดัชนีสำหรับรายการที่คุณต้องการระบุเป็นขีดจำกัดสูงสุดหรือต่ำสุด

เมธอด bound() จะระบุทั้งขีดจำกัดสูงสุดและต่ำสุด

IDBKeyRange.bound(lowerIndexKey, upperIndexKey);

ช่วงสำหรับฟังก์ชันเหล่านี้จะรวมอยู่โดยค่าเริ่มต้น ซึ่งหมายความว่าจะรวมข้อมูลที่ระบุว่าเป็นขีดจำกัดของช่วง หากต้องการละเว้นค่าเหล่านั้น ให้ระบุช่วงเป็น EXCLU โดยส่งผ่าน true เป็นอาร์กิวเมนต์ที่ 2 สำหรับ lowerBound() หรือ upperBound() หรือเป็นอาร์กิวเมนต์ที่ 3 และ 4 ของ bound() สำหรับขีดจำกัดต่ำสุดและสูงสุดตามลำดับ

ตัวอย่างถัดไปใช้ดัชนีในพร็อพเพอร์ตี้ 'price' ในที่เก็บออบเจ็กต์ 'foods' นอกจากนี้ Store ยังมีแบบฟอร์มแนบมาด้วยพร้อมอินพุต 2 รายการสำหรับขีดจำกัดสูงสุดและต่ำสุดของช่วง ใช้รหัสต่อไปนี้เพื่อค้นหาอาหารที่มีราคาอยู่ระหว่างขีดจำกัด

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

โค้ดตัวอย่างจะได้รับค่าสำหรับขีดจำกัดก่อน และตรวจสอบว่ามีขีดจำกัดหรือไม่ บล็อกถัดไปของโค้ดจะเลือกวิธีที่จะใช้ในการจำกัดช่วงตามค่า ในการโต้ตอบกับฐานข้อมูล ให้เปิดที่เก็บออบเจ็กต์ในธุรกรรมตามปกติ จากนั้นเปิดดัชนี 'price' ในที่เก็บออบเจ็กต์ ดัชนี 'price' ช่วยให้คุณค้นหาสินค้าตามราคาได้

จากนั้นโค้ดจะเปิดเคอร์เซอร์บนดัชนีและส่งผ่านในช่วง เคอร์เซอร์จะแสดงผลคำสัญญาที่แสดงถึงออบเจ็กต์แรกในช่วง หรือแสดงผล undefined หากไม่มีข้อมูลภายในช่วง เมธอด cursor.continue() จะแสดงผลเคอร์เซอร์ที่แสดงถึงออบเจ็กต์ถัดไป และวนซ้ำไปเรื่อยๆ จนกระทั่งถึงจุดสิ้นสุดของช่วง

การกำหนดเวอร์ชันฐานข้อมูล

เมื่อเรียกใช้เมธอด openDB() คุณจะระบุหมายเลขเวอร์ชันฐานข้อมูลในพารามิเตอร์ที่ 2 ได้ ในตัวอย่างทั้งหมดในคู่มือนี้ มีการตั้งค่าเวอร์ชันนี้เป็น 1 แต่คุณอัปเกรดฐานข้อมูลเป็นเวอร์ชันใหม่ได้หากคุณต้องการแก้ไขในทางใดทางหนึ่ง หากเวอร์ชันที่ระบุสูงกว่าเวอร์ชันของฐานข้อมูลที่มีอยู่ โค้ดเรียกกลับ upgrade ในออบเจ็กต์เหตุการณ์จะทำงาน ซึ่งทำให้คุณเพิ่มที่เก็บออบเจ็กต์และดัชนีใหม่ลงในฐานข้อมูลได้

ออบเจ็กต์ db ในโค้ดเรียกกลับ upgrade มีพร็อพเพอร์ตี้ oldVersion พิเศษ ซึ่งระบุหมายเลขเวอร์ชันของฐานข้อมูลที่เบราว์เซอร์มีสิทธิ์เข้าถึง คุณส่งหมายเลขเวอร์ชันนี้ไปยังคำสั่ง switch เพื่อเรียกใช้บล็อกโค้ดภายในโค้ดเรียกกลับ upgrade โดยอิงตามหมายเลขเวอร์ชันฐานข้อมูลที่มีอยู่ได้ ตัวอย่าง

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

ตัวอย่างนี้ตั้งค่าฐานข้อมูลเวอร์ชันล่าสุดเป็น 2 เมื่อโค้ดนี้ทำงานเป็นครั้งแรก ฐานข้อมูลยังไม่มีอยู่ในเบราว์เซอร์ ดังนั้น oldVersion จึงเป็น 0 และคำสั่ง switch จะเริ่มต้นที่ case 0 ในตัวอย่างนี้ จะเพิ่มที่เก็บออบเจ็กต์ 'store' ลงในฐานข้อมูล

ประเด็นสำคัญ: ในคำสั่ง switch โดยปกติจะมี break หลังจากทุกๆ case บล็อก แต่นั่นไม่ได้จงใจใช้ที่นี่ ด้วยวิธีนี้ หากฐานข้อมูลที่มีอยู่นั้นเป็นเวอร์ชันที่ล่าช้าแล้วหลายเวอร์ชันหรือหากไม่มีอยู่ โค้ดจะดำเนินต่อไปผ่านบล็อก case ที่เหลือจนกว่าจะเป็นปัจจุบัน ดังนั้นในตัวอย่างนี้ เบราว์เซอร์จะดําเนินการต่อผ่าน case 1 และสร้างดัชนี name ในที่เก็บออบเจ็กต์ store

หากต้องการสร้างดัชนี 'description' ในที่เก็บออบเจ็กต์ 'store' ให้อัปเดตหมายเลขเวอร์ชันและเพิ่มบล็อก case ใหม่ดังนี้

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

หากฐานข้อมูลที่คุณสร้างไว้ในตัวอย่างก่อนหน้านี้ยังคงอยู่ในเบราว์เซอร์ เมื่อเรียกใช้งาน oldVersion จะเป็น 2 เบราว์เซอร์จะข้าม case 0 และ case 1 และเรียกใช้โค้ดใน case 2 ซึ่งจะสร้างดัชนี description หลังจากนั้น เบราว์เซอร์จะมีฐานข้อมูลในเวอร์ชัน 3 ที่มีที่เก็บออบเจ็กต์ store ที่มีดัชนี name และ description

อ่านเพิ่มเติม

ทรัพยากรต่อไปนี้จะให้ข้อมูลและบริบทเพิ่มเติมสำหรับการใช้ IndexedDB

เอกสาร IndexedDB

ขีดจำกัดพื้นที่เก็บข้อมูล