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

Synchronous กับ Asynchronous storage

browser รัน JavaScript, layout และ painting บน main thread เดียว เมื่อ JavaScript call ใดใช้เวลานานในการ return จะหน่วงทุกอย่างที่เหลือ animations จะกระตุก clicks จะไม่ตอบสนอง และ browser อาจแสดงคำเตือน “page not responding” ปัญหานี้เรียกว่า jank

localStorage, sessionStorage และ document.cookie ล้วนเป็น synchronous เมื่อคุณเรียก localStorage.getItem('key') browser จะอ่านจาก disk คืนค่า และโค้ดของคุณจึงจะดำเนินต่อ script ของคุณไม่สามารถทำอะไรอื่นระหว่างรอ

สำหรับการอ่านเล็กน้อยครั้งเดียว ผลกระทบนี้แทบมองไม่เห็น แต่ปัญหาจะเกิดขึ้นเมื่อ:

  • วนลูปผ่าน keys จำนวนหลายพัน
  • เก็บ serialised objects ขนาดใหญ่ (หลาย kilobytes ต่อรายการ)
  • เขียนทุก keystroke หรือ scroll event
  • parse cookie string ทั้งหมดบน hot code path

เนื่องจากทั้งหมดนี้เกิดขึ้นบน main thread การใช้ Web Storage อย่างหนักจึงนำไปสู่ frame-time delays ที่วัดได้

// ทุก call เหล่านี้เป็น synchronous — thread จะ block จนแต่ละอันเสร็จ
const start = performance.now();
for (let i = 0; i < 1000; i++) {
localStorage.setItem('key-' + i, 'value-' + i);
}
const elapsed = performance.now() - start;
console.log('1 000 writes took ' + elapsed.toFixed(2) + ' ms');

IndexedDB และ Cache API เป็น asynchronous ทุก operation คืน Promise (หรือยิง event) และโค้ดของคุณดำเนินต่อทันที I/O จริงเกิดขึ้นนอก main thread เมื่อผลลัพธ์พร้อม browser จะเรียก callback หรือ resolve Promise ของคุณ

// return ทันที — การเขียนเกิดขึ้นใน background
const db = await idb.openDB('my-db', 1, {
upgrade(db) { db.createObjectStore('items'); }
});
await db.put('items', { name: 'Alice' }, 'user-1');
console.log('Write complete — main thread was never blocked');
flowchart LR
  subgraph Sync["Synchronous (Web Storage / Cookies)"]
    direction TB
    T1[Main thread] -->|blocked| R1[localStorage.getItem]
    R1 -->|returns| T1
  end
  subgraph Async["Asynchronous (IndexedDB / Cache API)"]
    direction TB
    T2[Main thread] -->|fires request| IDB[IndexedDB / Cache]
    T2 -->|continues immediately| T2
    IDB -->|Promise resolves| T2
  end
Synchronous storage block thread ที่เรียก; asynchronous storage ให้ thread ทำงานต่อได้

snippet ด้านล่างนับเวลา localStorage.setItem จำนวน 10 000 ครั้งแบบ synchronous บันทึกเวลาที่ผ่านไป จากนั้นทำความสะอาด key ที่สร้างขึ้นทั้งหมดที่ขึ้นต้นด้วย demo:

Browser Storage
ตัวเลือกBenefitCost
Synchronous (Web Storage / cookies)API เรียบง่าย เขียนโค้ดบรรทัดเดียวได้ผลลัพธ์ทันที ไม่ต้อง awaitblock main thread ทุกครั้งที่เรียก ทำให้เกิด jank เมื่อข้อมูลใหญ่หรือเรียกถี่
Asynchronous (IndexedDB / Cache API)ไม่ block main thread รองรับข้อมูลขนาดใหญ่และ operation จำนวนมากได้อย่างลื่นไหลโค้ดซับซ้อนขึ้น ต้องจัดการ Promise/callback และ error handling เพิ่ม
OPFS createSyncAccessHandle() ใน Workerให้ synchronous I/O ที่เร็วมากโดยไม่กระทบ main threadใช้ได้เฉพาะใน Web Worker เท่านั้น ต้องออกแบบสถาปัตยกรรมแยก thread ตั้งแต่ต้น
  • วนลูปเขียน localStorage จำนวนมากใน hot path — เช่นทุก keystroke หรือ scroll event ทำให้ main thread ถูก block ซ้ำ ๆ จนเกิด jank ที่ผู้ใช้สัมผัสได้จริง
  • คิดว่า async แปลว่าเร็วกว่าเสมอ — asynchronous storage ไม่ block thread ก็จริง แต่ operation เดี่ยว ๆ อาจใช้เวลานานกว่า synchronous call เล็กน้อยเพราะมี overhead ของ event loop และ IPC
  • ลืมว่า document.cookie ก็เป็น synchronous เหมือนกัน — หลายคนโฟกัสแค่ localStorage แต่การ parse cookie string ยาว ๆ บน hot code path ก็ block main thread ไม่ต่างกัน

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

Gmail offline mode — ใช้ IndexedDB (async) เก็บ email ที่ sync ไว้สำหรับใช้งาน offline เพราะข้อมูลมีปริมาณมากเกินกว่าที่ synchronous API จะรับมือได้โดยไม่ทำให้ UI ค้าง

Twitter Lite (PWA) — เลือกใช้ IndexedDB และ Cache API แทน localStorage สำหรับ cache ข้อมูล timeline เพื่อให้ scrolling และ interaction ยังคงลื่นไหลแม้ cache จะมีขนาดใหญ่

ทำไมการใช้ localStorage อย่างหนักจึงทำให้เกิด UI jank?
storage API ใดต่อไปนี้ที่เป็น asynchronous?
asynchronous storage call return อะไรทันทีให้กับผู้เรียก?