Background Sync
Background Sync
หัวข้อที่มีชื่อว่า “Background Sync”กลยุทธ์ offline ส่วนใหญ่มุ่งเน้นที่การอ่าน content ที่ cache ไว้ แต่การ เขียน ล่ะ — การส่งฟอร์ม, ข้อความแชท, การซื้อ? ถ้าผู้ใช้ส่งข้อมูลขณะ offline การ implement แบบง่าย ๆ จะแสดง error หรือ drop request เงียบ ๆ Background Sync API แก้ปัญหานี้โดยให้คุณ queue operation และให้ service worker replay อัตโนมัติเมื่อการเชื่อมต่อกลับมา แม้ผู้ใช้ปิด tab ไปแล้ว
วิธีทำงาน
หัวข้อที่มีชื่อว่า “วิธีทำงาน”ขั้นตอนมีสามฝ่าย: หน้าเว็บ, service worker, และ โครงสร้างพื้นฐาน sync ของ browser
- หน้าเว็บตรวจพบการเขียนที่ล้มเหลว (หรืออาจ offline)
- หน้าเว็บเรียก
registration.sync.register('tag-name')เพื่อตั้งเวลา sync event - browser fire
syncevent บน service worker เมื่อมีการเชื่อมต่อที่เสถียร SW retry operation และเรียกevent.waitUntil()เพื่อให้ browser ทำงานต่อจนงานเสร็จ - ถ้าการ sync ล้มเหลว browser จะ retry ด้วย exponential back-off
รองรับ browser
หัวข้อที่มีชื่อว่า “รองรับ browser”Background Sync ปัจจุบันรองรับเฉพาะ browser ที่ใช้ Chromium (Chrome, Edge, Samsung Internet, และ browser Android ส่วนใหญ่) Firefox และ Safari ไม่ได้ implement ทำให้ graceful fallback เป็นสิ่งจำเป็น — ถ้า API ไม่พร้อมใช้งาน ควรลอง network request ทันที
Feature detection
หัวข้อที่มีชื่อว่า “Feature detection”async function registerSync(registration) { if ('SyncManager' in window && registration.sync) { await registration.sync.register('send-form'); console.log('Sync registered — will retry when online'); } else { // Browser does not support Background Sync; attempt immediately console.log('Background Sync not supported — sending now'); await sendQueuedData(); }}ตรวจสอบทั้ง 'SyncManager' in window และ registration.sync — Chromium เวอร์ชันเก่าบางรุ่น expose SyncManager แต่ไม่ expose registration.sync
ฝั่งหน้าเว็บ: queuing การเขียน
หัวข้อที่มีชื่อว่า “ฝั่งหน้าเว็บ: queuing การเขียน”เมื่อผู้ใช้ submit ฟอร์ม ให้บันทึกข้อมูลลง IndexedDB ก่อน จากนั้น register sync tag การเขียนลง IndexedDB คือ record ที่คงทน; sync tag เป็นเพียง signal ให้ service worker
async function handleSubmit(formData) { // 1. Save to IndexedDB so the SW can read it later await saveToIdb('outbox', { id: Date.now(), ...formData });
// 2. Get the SW registration const registration = await navigator.serviceWorker.ready;
if (registration.sync) { // 3. Register the background sync tag await registration.sync.register('send-form'); } else { // Fallback: try the request right now await sendQueuedData(); }}ฝั่ง service worker: replay queued data
หัวข้อที่มีชื่อว่า “ฝั่ง service worker: replay queued data”Service worker ฟัง sync event property event.tag ตรงกับ string ที่ส่งให้ registration.sync.register()
self.addEventListener('sync', (event) => { if (event.tag === 'send-form') { event.waitUntil(replayOutbox()); }});
async function replayOutbox() { const db = await openDatabase(); const tx = db.transaction('outbox', 'readwrite'); const store = tx.objectStore('outbox'); const allRequests = await getAllRecords(store);
for (const item of allRequests) { const response = await fetch('/api/submit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(item), });
if (response.ok) { // Remove from outbox after successful send await deleteRecord(store, item.id); } }}ถ้า replayOutbox() throw หรือคืนค่า rejected Promise browser จะถือว่า sync ล้มเหลวและ schedule retry resolve สำเร็จเฉพาะเมื่อคุณแน่ใจว่าข้อมูลถูกส่งแล้ว
Sequence diagram
หัวข้อที่มีชื่อว่า “Sequence diagram”sequenceDiagram
participant U as User
participant P as Page
participant IDB as IndexedDB
participant SW as Service Worker
participant API as Server API
U->>P: Submits form (offline)
P->>IDB: saveToIdb('outbox', data)
P->>SW: registration.sync.register('send-form')
Note over U,SW: Connection restored
SW->>SW: sync event fires (tag: send-form)
SW->>IDB: read all outbox records
IDB-->>SW: queued items
SW->>API: POST /api/submit
API-->>SW: 200 OK
SW->>IDB: delete sent record Interactive demo
หัวข้อที่มีชื่อว่า “Interactive demo”Playground ด้านล่างแสดง flow ทั้งหมด หน้าเว็บให้คุณ submit ข้อความ Service worker จะ queue ลงใน in-memory outbox และ register sync tag เมื่อ browser fire sync event SW จะ log การ replay Toggle แผงเครือข่ายของ browser เป็น “Offline” ก่อน submit เพื่อดู queue ทำงานจริง
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
| ใช้ Background Sync API | retry request ที่ล้มเหลวอัตโนมัติแม้ปิด tab ไปแล้ว | รองรับเฉพาะบาง browser (ไม่มีใน Safari) ต้องมี fallback เอง |
| Retry ด้วยมือตอนกลับมา online (foreground เท่านั้น) | ทำงานได้ทุก browser ไม่ต้องพึ่ง API พิเศษ | พลาด sync ถ้าผู้ใช้ปิด tab ไปก่อนที่ network จะกลับมา |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- เก็บ mutation ไว้ใน memory แทน IndexedDB ทำให้ข้อมูลหายทันทีที่ service worker ถูก terminate
- ไม่ทำ retry backoff ทำให้ sync tag ยิง request รัวๆ ซ้ำเมื่อ network กลับมาแต่ server ยัง unstable
- ไม่ handle กรณี sync ล้มเหลวซ้ำ (permanent failure) ทำให้ queue ค้างพยายาม sync ข้อมูลที่ไม่มีทาง sync สำเร็จตลอดไป
💡 ตัวอย่างจากของจริง
Google Maps — คิว location update และ report ไว้ผ่าน Background Sync แล้วส่งอัตโนมัติทันทีที่กลับมามีสัญญาณ
Notion offline — เก็บการแก้ไขที่ทำตอน offline ไว้ในคิว แล้ว sync กลับ server อัตโนมัติด้วยกลไกคล้าย background sync เมื่อกลับมา online