ข้ามไปยังเนื้อหา

CRUD Operations

เปิด database และได้ object store มาแล้ว แต่จะเพิ่ม อ่าน แก้ไข หรือลบ record จริง ๆ ต้องเรียก method ไหนบ้าง — และแต่ละตัวต้องการ transaction mode แบบไหน หน้านี้คือ reference ของ method ทั้งหมดที่ใช้

ทุก operation ข้อมูลใน IndexedDB ผ่าน IDBObjectStore คุณได้รับ store จาก transaction แล้วเรียกใช้หนึ่งใน method หกตัวนี้:

MethodMode ที่ต้องใช้คำอธิบาย
add(value)readwriteเพิ่ม record ใหม่; โยน ConstraintError ถ้า key ซ้ำ
put(value)readwriteเพิ่มหรือเขียนทับ (upsert); ไม่โยน error เมื่อ key ซ้ำ
get(key)readonlyคืน record เดียว หรือ undefined ถ้าไม่มี key นั้น
getAll()readonlyคืน array ของ record ทั้งหมดใน store
delete(key)readwriteลบ record ที่มี key ที่ระบุ; ไม่มีผลถ้าไม่พบ
count()readonlyคืนจำนวน record ทั้งหมด

ทุก method คืน IDBRequest โดยผลลัพธ์จะพร้อมใช้งานภายใน callback onsuccess ของ request ในรูป request.result

add(value) ใช้สำหรับ record ใหม่ เท่านั้น หาก record ที่มี key path value เดิมมีอยู่แล้ว request จะเรียก onerror พร้อม ConstraintError และไม่มีการเขียนข้อมูล

put(value) คือ upsert: เขียน record ไม่ว่าจะมี key อยู่แล้วหรือไม่ ใช้ put เมื่อต้องการพฤติกรรม “สร้างหรืออัปเดต” โดยไม่ต้องตรวจสอบก่อน

const tx = db.transaction(['products'], 'readwrite');
const store = tx.objectStore('products');
// add — ปลอดภัยเฉพาะเมื่อ id 1 ยังไม่มีอยู่
store.add({ id: 1, name: 'Apple', price: 1.5 });
// put — ปลอดภัยเสมอ; เขียนทับถ้า id 1 มีอยู่แล้ว
store.put({ id: 1, name: 'Apple', price: 1.99 });

ต่างจาก Web Storage ที่ key ที่ไม่มีอยู่จะคืน null ใน IndexedDB นั้น get(key) จะ resolve เป็น undefined เมื่อไม่มี record ที่ตรงกัน ให้ตรวจสอบ === undefined เสมอ ไม่ใช่ !result เพื่อแยกแยะ record ที่ไม่มีออกจาก record ที่เก็บค่า falsy

const tx = db.transaction(['products'], 'readonly');
const store = tx.objectStore('products');
const req = store.get(999);
req.onsuccess = () => {
console.log(req.result); // undefined — key 999 ไม่มีอยู่
};

callback แบบ IDBRequest ดิบๆ นั้นยาว helper เล็กๆ ช่วยให้ทุก store method ใช้กับ await ได้:

function promisifyReq(r) {
return new Promise((res, rej) => {
r.onsuccess = () => res(r.result);
r.onerror = () => rej(r.error);
});
}
// ตอนนี้สามารถ await ทุก store method ได้
const apple = await promisifyReq(store.get(1));
Browser Storage
ตัวเลือกBenefitCost
add() เทียบกับ put()add ป้องกัน key ซ้ำด้วย ConstraintError ทำให้มั่นใจว่าเป็น record ใหม่จริงต้องเขียน error handling เพิ่มเพื่อจัดการกรณี key ซ้ำ ต่างจาก put ที่ upsert ให้เลย
getAll() เทียบกับการวน cursorเรียกครั้งเดียวได้ array ครบ เขียนโค้ดสั้น อ่านง่ายโหลดทุก record เข้า memory พร้อมกัน ไม่เหมาะกับ store ขนาดใหญ่
wrap IDBRequest ด้วย Promiseใช้ async/await อ่านง่าย ลด callback nestingเพิ่ม layer abstraction; ต้อง handle onerror เองให้ครบทุก request ไม่งั้น error หายเงียบ
  • เช็ค if (!result) แทน result === undefined — record ที่เก็บค่า falsy เช่น 0 หรือ '' จะถูกตีความว่าไม่มีอยู่ ทั้งที่จริงมี record นั้นอยู่ ต้องเช็ค === undefined เท่านั้น
  • ใช้ add() แล้วไม่ handle onerror — เมื่อ key ซ้ำ request จะโยน ConstraintError แต่ถ้าไม่มี onerror handler error จะหายไปเงียบ ๆ โดยที่ record ไม่ถูกเขียนแต่โค้ดไม่รู้ตัว
  • await fetch() แทรกกลางระหว่างเรียก store method — ทำให้ transaction ที่ store method นั้นสังกัดอยู่ auto-commit ไปก่อน request ถัดไปจะ throw TransactionInactiveError ทันที

💡 ตัวอย่างจากของจริง

Notion — ใช้ IndexedDB เก็บ workspace data สำหรับโหมด offline โดยเรียก put() แบบ upsert เพื่อ sync การเปลี่ยนแปลงจาก server กลับเข้า local store โดยไม่ต้องเช็คว่า record มีอยู่ก่อน

WhatsApp Web — เก็บประวัติข้อความด้วย add() เมื่อได้รับข้อความใหม่ และใช้ getAll() เพื่อโหลดบทสนทนาทั้งหมดตอนเปิดแชทขึ้นมาใหม่

คุณเรียก store.add({ id: 1, name: "Apple" }) ตอนที่ record ที่มี id 1 มีอยู่แล้ว จะเกิดอะไรขึ้น?
store.get(999) จะ resolve เป็นอะไรเมื่อไม่มี record ที่มี key 999?
ต้องใช้ transaction mode ใดเพื่อเรียก store.delete(key)?
คุณต้องการอัปเดต record ถ้ามีอยู่แล้ว หรือเพิ่มใหม่ถ้าไม่มี — ในการเรียกครั้งเดียว จะใช้ method ใด?