ดูแนวทางปฏิบัติแนะนำสำหรับการซิงค์สถานะของแอปพลิเคชันระหว่าง IndexedDB กับไลบรารีการจัดการสถานะยอดนิยม
เมื่อผู้ใช้โหลดเว็บไซต์หรือแอปพลิเคชันเป็นครั้งแรก มักจะมีงานค่อนข้างมากเกี่ยวกับการสร้างสถานะแอปพลิเคชันเริ่มต้นที่ใช้ในการแสดง UI ตัวอย่างเช่น บางครั้งแอปต้องตรวจสอบสิทธิ์ฝั่งไคลเอ็นต์ของผู้ใช้ แล้วส่งคำขอ API หลายรายการก่อนที่แอปจะมีข้อมูลทั้งหมดที่จำเป็นต่อการแสดงในหน้าเว็บ
การจัดเก็บสถานะของแอปพลิเคชันใน IndexedDB อาจเป็นวิธีที่ดีในการเพิ่มความเร็วในการโหลดสำหรับการเข้าชมซ้ำ จากนั้นแอปจะซิงค์กับบริการ API ในเบื้องหลัง และอัปเดต UI ด้วยข้อมูลใหม่แบบ Lazy Loading โดยใช้กลยุทธ์ไม่มีอัปเดตขณะตรวจสอบซ้ำ
ประโยชน์อีกข้อหนึ่งสำหรับ IndexedDB คือการจัดเก็บเนื้อหาที่ผู้ใช้สร้างขึ้น ไม่ว่าจะเป็นการจัดเก็บเนื้อหาชั่วคราวก่อนที่จะอัปโหลดไปยังเซิร์ฟเวอร์ หรือเป็นแคชข้อมูลระยะไกลที่ฝั่งไคลเอ็นต์ หรือทั้งคู่
อย่างไรก็ตาม เมื่อใช้ IndexedDB มีสิ่งสำคัญหลายอย่างที่ต้องพิจารณา ซึ่งอาจไม่ชัดเจนนักสำหรับนักพัฒนาที่เพิ่งเริ่มใช้ API บทความนี้จะตอบคำถามที่พบบ่อยและพูดถึงสิ่งสำคัญที่สุดบางประการที่ควรคำนึงถึงเมื่อคงข้อมูลใน IndexedDB
การทำให้แอปคาดการณ์ได้อยู่เสมอ
ความซับซ้อนมากมายเกี่ยวกับ IndexedDB เกิดจากข้อเท็จจริงที่ว่า มีปัจจัยต่างๆ มากมายที่คุณ (นักพัฒนาซอฟต์แวร์) ไม่มีสิทธิ์ควบคุม ส่วนนี้จะสำรวจปัญหาหลายอย่างที่คุณต้องคำนึงถึงเมื่อทำงานกับ IndexedDB
ไม่สามารถจัดเก็บทุกอย่างใน IndexedDB บนทุกแพลตฟอร์ม
หากคุณจัดเก็บไฟล์ขนาดใหญ่ที่ผู้ใช้สร้างขึ้น เช่น รูปภาพหรือวิดีโอ คุณอาจลองเก็บไฟล์เหล่านั้นเป็นออบเจ็กต์ File
หรือ Blob
การทำเช่นนี้จะใช้ได้ในบางแพลตฟอร์ม แต่ล้มเหลวในบางแพลตฟอร์ม โดยเฉพาะ Safari ใน
iOS จะไม่สามารถจัดเก็บ Blob
ใน IndexedDB
โชคดีที่การแปลง Blob
เป็น ArrayBuffer
ไม่ยากเกินไป และในทางกลับกันด้วย การจัดเก็บ ArrayBuffer
s ใน IndexedDB ได้รับการรองรับเป็นอย่างดี
อย่างไรก็ตาม อย่าลืมว่า Blob
มีประเภท MIME แต่ ArrayBuffer
ไม่มี คุณจะต้องจัดเก็บประเภทไว้ข้างบัฟเฟอร์เพื่อให้การแปลงเป็นไปอย่างถูกต้อง
ในการแปลง ArrayBuffer
เป็น Blob
เพียงแค่ใช้ตัวสร้าง Blob
function arrayBufferToBlob(buffer, type) {
return new Blob([buffer], { type: type });
}
อีกทิศทางหนึ่งจะเกี่ยวข้องมากกว่าเล็กน้อย และเป็นกระบวนการที่ไม่พร้อมกัน คุณสามารถใช้ออบเจ็กต์ FileReader
เพื่ออ่าน Blob เป็น ArrayBuffer
เมื่ออ่านค่าเสร็จแล้ว จะมีการทริกเกอร์เหตุการณ์ loadend
บนเครื่องอ่าน คุณสามารถรวมกระบวนการนี้ในรูปแบบ Promise
ดังนี้
function blobToArrayBuffer(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.addEventListener('loadend', () => {
resolve(reader.result);
});
reader.addEventListener('error', reject);
reader.readAsArrayBuffer(blob);
});
}
การเขียนไปยังพื้นที่เก็บข้อมูลอาจล้มเหลว
ข้อผิดพลาดเมื่อเขียนไปยัง IndexedDB อาจเกิดขึ้นได้จากหลายสาเหตุ และในบางกรณี สาเหตุเหล่านี้ก็อยู่นอกเหนือการควบคุมของคุณในฐานะนักพัฒนาซอฟต์แวร์ ตัวอย่างเช่น ปัจจุบันบางเบราว์เซอร์ไม่อนุญาตให้เขียนไปยัง IndexedDB เมื่ออยู่ในโหมดการเรียกดูแบบส่วนตัว นอกจากนี้ยังเป็นไปได้ที่ผู้ใช้จะใช้พื้นที่ในดิสก์จนเต็ม และเบราว์เซอร์จะจำกัดไม่ให้คุณจัดเก็บข้อมูลใดๆ เลย
ด้วยเหตุนี้ คุณจึงต้องใช้การจัดการข้อผิดพลาดที่เหมาะสมในโค้ด IndexedDB เสมอ นอกจากนี้ โดยทั่วไปคุณควรเก็บสถานะของแอปพลิเคชันไว้ในหน่วยความจำ (นอกเหนือจากการจัดเก็บ) เพื่อให้ UI ทำงานอย่างต่อเนื่องเมื่อทำงานในโหมดการเรียกดูแบบส่วนตัวหรือเมื่อไม่มีพื้นที่เก็บข้อมูลให้ใช้งาน (แม้ว่าฟีเจอร์อื่นๆ ของแอปที่ต้องใช้พื้นที่เก็บข้อมูลจะไม่ทำงานก็ตาม)
คุณสามารถตรวจหาข้อผิดพลาดในการดำเนินงาน IndexedDB โดยเพิ่มเครื่องจัดการเหตุการณ์สำหรับเหตุการณ์ error
ทุกครั้งที่คุณสร้างออบเจ็กต์ IDBDatabase
, IDBTransaction
หรือ IDBRequest
const request = db.open('example-db', 1);
request.addEventListener('error', (event) => {
console.log('Request error:', request.error);
};
ผู้ใช้อาจแก้ไขหรือลบข้อมูลที่จัดเก็บไว้ได้
ส่วนขยายเบราว์เซอร์และเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์จะเข้าถึงฐานข้อมูลฝั่งไคลเอ็นต์ได้ในฐานข้อมูลฝั่งไคลเอ็นต์ และผู้ใช้สามารถล้างฐานข้อมูลเหล่านี้ได้ ซึ่งต่างจากฐานข้อมูลฝั่งเซิร์ฟเวอร์ที่จำกัดการเข้าถึงโดยไม่ได้รับอนุญาตได้
แม้ว่าผู้ใช้จะแก้ไขข้อมูลที่เก็บไว้ในเครื่องได้ยาก แต่ผู้ใช้มักจะล้างข้อมูลออกอยู่แล้ว แอปพลิเคชันของคุณจำเป็นต้องจัดการทั้ง 2 กรณีนี้ได้โดยไม่เกิดข้อผิดพลาด
ข้อมูลที่จัดเก็บไว้อาจเก่าเกินไป
คล้ายกับส่วนก่อนหน้านี้ แม้ว่าผู้ใช้จะไม่ได้แก้ไขข้อมูลเอง แต่ก็เป็นไปได้ว่าข้อมูลที่ตนอยู่ในพื้นที่เก็บข้อมูลเขียนขึ้นโดยโค้ดเวอร์ชันเก่า ซึ่งอาจจะเป็นเวอร์ชันที่มีข้อบกพร่องอยู่ในนั้น
IndexedDB มีการสนับสนุนในตัวสำหรับเวอร์ชันสคีมาและการอัปเกรดผ่านเมธอด IDBOpenDBRequest.onupgradeneeded()
อย่างไรก็ตาม คุณยังคงต้องเขียนโค้ดการอัปเกรดให้รองรับผู้ใช้ที่มาจากเวอร์ชันก่อนหน้า (รวมถึงเวอร์ชันที่มีข้อบกพร่อง)
การทดสอบหน่วยจะมีประโยชน์มากสำหรับที่นี่ เนื่องจากการทดสอบเส้นทางการอัปเกรดและกรณีที่เป็นไปได้ทั้งหมดด้วยตนเองมักเป็นไปไม่ได้
ทำให้แอปมีประสิทธิภาพอยู่เสมอ
หนึ่งในฟีเจอร์หลักของ IndexedDB คือ API แบบไม่พร้อมกัน แต่อย่าปล่อยให้เรื่องนี้เข้าใจผิดว่าคุณไม่จำเป็นต้องกังวลเกี่ยวกับประสิทธิภาพเมื่อใช้ มีหลายกรณีที่การใช้งานที่ไม่เหมาะสมอาจยังบล็อกเทรดหลักได้ ซึ่งอาจทำให้เกิดความไม่เสถียรและไม่ตอบสนอง
ตามกฎทั่วไป การอ่านและเขียนไปยัง IndexedDB ไม่ควรมีขนาดใหญ่กว่าที่กำหนดสำหรับข้อมูลที่มีการเข้าถึง
แม้ว่า IndexedDB จะทำให้สามารถจัดเก็บออบเจ็กต์ขนาดใหญ่ที่ซ้อนกันเป็นระเบียนเดียวได้ (ซึ่งเป็นที่ยอมรับกันค่อนข้างง่ายในแง่ของนักพัฒนาซอฟต์แวร์) จึงควรหลีกเลี่ยงการกระทำนี้ เนื่องจากเมื่อ IndexedDB จัดเก็บออบเจ็กต์ จะต้องมีการสร้างการโคลนที่มีโครงสร้างของออบเจ็กต์นั้นก่อน และกระบวนการโคลนที่มีโครงสร้างจะเกิดขึ้นในเทรดหลัก ยิ่งออบเจ็กต์มีขนาดใหญ่เท่าใด ระยะเวลาการบล็อกก็จะยิ่งนานขึ้นเท่านั้น
ปัญหานี้ก่อให้เกิดความท้าทายในการวางแผนเกี่ยวกับวิธีคงสถานะของแอปพลิเคชันไว้ใน IndexedDB เนื่องจากไลบรารีการจัดการสถานะยอดนิยมส่วนใหญ่ (เช่น Redux) ทำงานด้วยการจัดการแผนผังสถานะทั้งหมดเป็นออบเจ็กต์ JavaScript รายการเดียว
แม้ว่าการจัดการสถานะในลักษณะนี้มีประโยชน์หลายอย่าง (เช่น ทำให้โค้ดหาเหตุผลและแก้ไขข้อบกพร่องได้ง่าย) และแม้การจัดเก็บโครงสร้างสถานะทั้งหมดเป็นระเบียนเดียวใน IndexedDB ก็อาจจะน่าดึงดูดและสะดวก แต่การทำเช่นนี้หลังการเปลี่ยนแปลงทุกครั้ง (แม้จะควบคุม/ดีเดน) จะส่งผลให้มีการบล็อกเทรดหลักโดยไม่จำเป็น แต่ก็จะเพิ่มโอกาสในการเขียนข้อผิดพลาดหรือแม้แต่ในกรณีที่เบราว์เซอร์ไม่ตอบสนอง
แทนที่จะจัดเก็บโครงสร้างสถานะทั้งหมดไว้ในระเบียนเดียว คุณควรแยกออกเป็นระเบียนย่อยๆ และอัปเดตเฉพาะระเบียนที่มีการเปลี่ยนแปลงจริงๆ เท่านั้น
และระบบจะทำเช่นเดียวกันหากคุณจัดเก็บรายการขนาดใหญ่ เช่น รูปภาพ เพลง หรือวิดีโอ ไว้ใน IndexedDB จัดเก็บแต่ละรายการด้วยคีย์ของตัวเองแทนที่จะเก็บอยู่ในออบเจ็กต์ที่ใหญ่กว่า เพื่อให้คุณดึงข้อมูลที่มีโครงสร้างได้โดยไม่ต้องเสียค่าใช้จ่ายในการเรียกไฟล์ไบนารีด้วย
เช่นเดียวกับแนวทางปฏิบัติแนะนำส่วนใหญ่ กฎนี้ไม่ใช่กฎเกณฑ์ตายตัว ในกรณีที่ไม่สามารถแยกออบเจ็กต์สถานะแล้วเขียนเพียงชุดการเปลี่ยนแปลงที่น้อยที่สุด การแบ่งข้อมูลออกเป็นโครงสร้างย่อยๆ และการเขียนเพียงแผนผังสถานะยังดีกว่าการเขียนแผนผังสถานะทั้งหมดเสมอ ปรับปรุงเล็กน้อยดีกว่าไม่ต้องปรับปรุงเลย
สุดท้ายนี้ คุณควรวัดผลกระทบด้านประสิทธิภาพของโค้ดที่คุณเขียนเสมอ แม้ว่าจะเขียนขนาดเล็กลงใน IndexedDB จะทำงานได้ดีกว่าการเขียนขนาดใหญ่ แต่ก็จำเป็นเฉพาะในกรณีที่การเขียนใน IndexedDB ที่แอปพลิเคชันกำลังทำอยู่จริงๆ นำไปสู่งานที่ใช้เวลานานซึ่งบล็อกเทรดหลักและทำให้ประสบการณ์ของผู้ใช้แย่ลง การวัดผลเป็นสิ่งสำคัญ เพื่อให้เข้าใจว่ากำลังเพิ่มประสิทธิภาพอะไร
บทสรุป
นักพัฒนาซอฟต์แวร์สามารถใช้ประโยชน์จากกลไกพื้นที่เก็บข้อมูลของไคลเอ็นต์ เช่น IndexedDB เพื่อปรับปรุงประสบการณ์ของผู้ใช้ในแอปพลิเคชันของตน โดยไม่เพียงรักษาสถานะในเซสชันต่างๆ เท่านั้น แต่ยังลดเวลาที่ใช้ในการโหลดสถานะเริ่มต้นเมื่อมีการเข้าชมซ้ำ
ขณะที่การใช้ IndexedDB อย่างเหมาะสมจะช่วยปรับปรุงประสบการณ์ของผู้ใช้ได้อย่างมาก แต่การใช้ IndexedDB อย่างไม่ถูกต้องหรือไม่จัดการกับกรณีข้อผิดพลาดอาจทำให้แอปเสียหายและผู้ใช้ไม่พอใจ
เนื่องจากพื้นที่เก็บข้อมูลของไคลเอ็นต์มีหลายปัจจัยที่อยู่เหนือการควบคุมของคุณ โค้ดจึงได้รับการทดสอบเป็นอย่างดีและจัดการกับข้อผิดพลาดอย่างเหมาะสม แม้ว่าข้อผิดพลาดที่อาจจะดูเหมือนไม่เกิดขึ้นในตอนแรกก็ตาม