fetch Event
fetch event
หัวข้อที่มีชื่อว่า “fetch event”เมื่อ service worker ถูก activate และควบคุมหน้าเว็บแล้ว ทุก network request ที่หน้าเว็บส่งออกไป — HTML, CSS, JS, รูปภาพ, การเรียก API — จะผ่าน fetch event ของ SW นี่คือหัวใจของ PWA แบบ offline-first
event.respondWith()
หัวข้อที่มีชื่อว่า “event.respondWith()”ภายใน listener ของ fetch ให้เรียก event.respondWith() ด้วย Promise ที่ resolve ออกมาเป็น Response ถ้าคุณไม่เรียก event.respondWith() request จะตกไปยัง network ตามปกติ (พฤติกรรมแบบ passthrough)
self.addEventListener('fetch', (event) => { // Simplest possible passthrough — behaves as if no SW is installed event.respondWith(fetch(event.request));});กลยุทธ์ network-first
หัวข้อที่มีชื่อว่า “กลยุทธ์ network-first”ลอง network ก่อน ถ้าล้มเหลว (ออฟไลน์หรือ server error) ค่อย fall back ไปที่ cache
self.addEventListener('fetch', (event) => { event.respondWith( fetch(event.request) .then((response) => { // Clone the response: a Response body can only be consumed once const clone = response.clone(); caches.open('v1').then((cache) => cache.put(event.request, clone)); return response; }) .catch(() => caches.match(event.request)) );});กลยุทธ์ cache-first
หัวข้อที่มีชื่อว่า “กลยุทธ์ cache-first”เสิร์ฟจาก cache ทันที ถ้ายังไม่ได้ cache ไว้ค่อยไปที่ network แล้ว cache ผลลัพธ์ไว้
self.addEventListener('fetch', (event) => { event.respondWith( caches.match(event.request).then((cached) => { if (cached) return cached; return fetch(event.request).then((response) => { const clone = response.clone(); caches.open('v1').then((cache) => cache.put(event.request, clone)); return response; }); }) );});กลยุทธ์ stale-while-revalidate
หัวข้อที่มีชื่อว่า “กลยุทธ์ stale-while-revalidate”คืนเวอร์ชันที่ cache ไว้ในทันที แล้วค่อยอัปเดต cache อยู่เบื้องหลัง
self.addEventListener('fetch', (event) => { event.respondWith( caches.open('v1').then((cache) => cache.match(event.request).then((cached) => { const networkFetch = fetch(event.request).then((response) => { cache.put(event.request, response.clone()); return response; }); return cached || networkFetch; }) ) );});ไดอะแกรมการไหลของ request
หัวข้อที่มีชื่อว่า “ไดอะแกรมการไหลของ request”sequenceDiagram
participant P as Page
participant SW as Service Worker
participant C as Cache Storage
participant N as Network
P->>SW: fetch(request)
SW->>C: caches.match(request)
alt cache hit
C-->>SW: cached Response
SW-->>P: Response (instant)
else cache miss
SW->>N: fetch(request)
N-->>SW: fresh Response
SW->>C: cache.put(request, response.clone())
SW-->>P: Response
end หลุมพราง
หัวข้อที่มีชื่อว่า “หลุมพราง”clone ก่อน cache เสมอ body ของ Response คือ readable stream ที่ consume ได้เพียงครั้งเดียว ถ้าคุณส่ง Response ตัวเดียวกันให้ทั้ง caller และ cache.put() ฝั่งใดฝั่งหนึ่งจะได้ body ว่างเปล่า ใช้ response.clone() ก่อนจัดเก็บ
อย่าดักจับ cross-origin opaque response อย่างประมาท request ไปยัง third-party origin ที่ไม่มี CORS จะคืน opaque response (type === 'opaque') ที่มี status === 0 การ cache opaque response ทำให้พื้นที่จัดเก็บ cache บวมขึ้นอย่างคาดเดาไม่ได้ ใช้ตรรกะของ fetch-event เฉพาะกับ request แบบ same-origin เท่านั้น เว้นแต่คุณจะรู้ว่ากำลังทำอะไรอยู่
self.addEventListener('fetch', (event) => { // Only handle same-origin requests if (!event.request.url.startsWith(self.location.origin)) return;
event.respondWith( caches.match(event.request).then((cached) => cached || fetch(event.request)) );});ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
| Intercept ทุก fetch event และ respondWith() เอง | ควบคุม caching strategy ได้ละเอียดทุก request | ทุก request ต้อง handle ให้ถูก ไม่งั้น fetch อาจ hang หรือ error |
| ปล่อยให้ request ผ่านไปหา network ตามปกติ (ไม่ intercept) | โค้ดง่าย ไม่เสี่ยง bug จาก caching logic | ไม่ได้ประโยชน์ offline หรือความเร็วจาก cache เลย |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- ไม่เรียก event.respondWith() เลยในบาง path ทำให้ browser fallback ไป network แบบไม่ตั้งใจ (เงียบๆ ไม่มี error)
- cache response ของ POST/PUT request ที่ไม่ควร cache ทำให้ mutation ทำงานผิดพลาดตอน replay จาก cache
- ไม่เช็ค request.mode หรือ request.method ก่อน intercept ทำให้ fetch handler ไปยุ่งกับ request ที่ควรปล่อยผ่าน เช่น cross-origin analytics call
💡 ตัวอย่างจากของจริง
Gmail — fetch handler ของ service worker แยก strategy ระหว่าง static asset กับ API call ของอีเมลอย่างชัดเจน ทำให้ offline ยังเปิดอีเมลที่เคยอ่านได้
Google Docs — intercept fetch event เพื่อ serve เอกสารจาก cache ทันทีเมื่อออฟไลน์ แล้ว sync การแก้ไขกลับตอนกลับมา online