CRUD Operations
เปิด database และได้ object store มาแล้ว แต่จะเพิ่ม อ่าน แก้ไข หรือลบ record จริง ๆ ต้องเรียก method ไหนบ้าง — และแต่ละตัวต้องการ transaction mode แบบไหน หน้านี้คือ reference ของ method ทั้งหมดที่ใช้
ตารางอ้างอิง method CRUD
หัวข้อที่มีชื่อว่า “ตารางอ้างอิง method CRUD”ทุก operation ข้อมูลใน IndexedDB ผ่าน IDBObjectStore คุณได้รับ store จาก transaction แล้วเรียกใช้หนึ่งใน method หกตัวนี้:
| Method | Mode ที่ต้องใช้ | คำอธิบาย |
|---|---|---|
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 เทียบกับ put
หัวข้อที่มีชื่อว่า “add เทียบกับ put”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 });get คืน undefined ไม่ใช่ null
หัวข้อที่มีชื่อว่า “get คืน undefined ไม่ใช่ null”ต่างจาก 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 ไม่มีอยู่};การ wrap IDBRequest ด้วย Promise
หัวข้อที่มีชื่อว่า “การ wrap IDBRequest ด้วย Promise”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));Runnable: ตัวอย่าง CRUD ครบทุกขั้นตอน
หัวข้อที่มีชื่อว่า “Runnable: ตัวอย่าง CRUD ครบทุกขั้นตอน”ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
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()แล้วไม่ handleonerror— เมื่อ key ซ้ำ request จะโยนConstraintErrorแต่ถ้าไม่มีonerrorhandler error จะหายไปเงียบ ๆ โดยที่ record ไม่ถูกเขียนแต่โค้ดไม่รู้ตัว await fetch()แทรกกลางระหว่างเรียก store method — ทำให้ transaction ที่ store method นั้นสังกัดอยู่ auto-commit ไปก่อน request ถัดไปจะ throwTransactionInactiveErrorทันที
💡 ตัวอย่างจากของจริง
Notion — ใช้ IndexedDB เก็บ workspace data สำหรับโหมด offline โดยเรียก
put()แบบ upsert เพื่อ sync การเปลี่ยนแปลงจาก server กลับเข้า local store โดยไม่ต้องเช็คว่า record มีอยู่ก่อนWhatsApp Web — เก็บประวัติข้อความด้วย
add()เมื่อได้รับข้อความใหม่ และใช้getAll()เพื่อโหลดบทสนทนาทั้งหมดตอนเปิดแชทขึ้นมาใหม่