Lifecycle ของ Service Worker
lifecycle ของ service worker
หัวข้อที่มีชื่อว่า “lifecycle ของ service worker”service worker ทุกตัวจะเคลื่อนผ่าน state machine ที่ browser กำหนดไว้ตายตัว การเข้าใจ lifecycle นี้เป็นเรื่องจำเป็น — ข้ามไปแล้วการอัปเดตของคุณจะ activate ไม่สำเร็จแบบเงียบ ๆ หรือคุณทำให้หน้าเว็บที่ยังรัน worker ตัวเก่าอยู่พังได้
state machine
หัวข้อที่มีชื่อว่า “state machine”stateDiagram-v2 [*] --> Parsed : browser parses sw.js Parsed --> Installing : install event fires Installing --> Installed : installEvent.waitUntil() resolves Installing --> Redundant : waitUntil() rejects Installed --> Activating : no old SW active, or skipWaiting() Installed --> Waiting : old SW still controlling pages Waiting --> Activating : all old-SW tabs closed, or skipWaiting() Activating --> Activated : activateEvent.waitUntil() resolves Activating --> Redundant : waitUntil() rejects Activated --> Redundant : replaced by a newer SW Waiting --> Redundant : replaced by a newer SW
install event
หัวข้อที่มีชื่อว่า “install event”install event จะ fire ทันทีที่ browser parse และดาวน์โหลดไฟล์ service worker ของคุณเสร็จ นี่เป็นจังหวะที่เหมาะที่สุดในการ pre-cache ทรัพยากรที่แอปของคุณต้องใช้สำหรับการใช้งานออฟไลน์
const CACHE_NAME = 'v1';const PRECACHE_URLS = ['/', '/styles.css', '/app.js'];
self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE_URLS)) );});event.waitUntil() รับ Promise เข้ามา ถ้า Promise resolve SW จะย้ายไปสถานะ Installed ถ้า Promise reject (เช่น เกิด network error ระหว่าง pre-cache) SW จะย้ายไป Redundant และถูกทิ้ง — ไม่มีอะไรพังสำหรับผู้ใช้ SW ตัวเก่ายังคงทำงานต่อไป
สถานะ waiting
หัวข้อที่มีชื่อว่า “สถานะ waiting”หลังจากติดตั้งแล้ว SW ตัวใหม่จะนั่งรออยู่ในสถานะ Waiting ถ้ายังมีแท็บใด ๆ ที่ถูกควบคุมโดยเวอร์ชันก่อนหน้าอยู่ browser จะไม่ activate SW ตัวใหม่จนกว่าทุกแท็บที่รันตัวเก่าจะถูกปิด (หรือ navigate ออกไป)
หากต้องการข้ามช่วงรอด้วยโค้ด ให้เรียก self.skipWaiting() ภายใน handler ของ install:
self.addEventListener('install', (event) => { event.waitUntil( caches.open('v1').then((cache) => cache.addAll(['/'])).then(() => self.skipWaiting()) );});activate event
หัวข้อที่มีชื่อว่า “activate event”เมื่อ SW ตัวเก่าหายไปแล้ว (หรือมีการเรียก skipWaiting()) SW ตัวใหม่จะ fire activate event ของตัวเอง นี่เป็นจังหวะที่เหมาะในการ ล้าง cache เก่า เพื่อไม่ให้ asset ที่ล้าสมัยกินพื้นที่จัดเก็บไปเรื่อย ๆ ไม่รู้จบ
const CURRENT_CACHES = ['v2'];
self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((names) => Promise.all( names .filter((name) => !CURRENT_CACHES.includes(name)) .map((name) => caches.delete(name)) ) ).then(() => self.clients.claim()) );});self.clients.claim() ทำให้ SW ที่เพิ่ง activate เข้าควบคุมทุกหน้าที่เปิดอยู่ได้ทันที โดยไม่ต้องรอ navigation ครั้งถัดไป
สรุป lifecycle
หัวข้อที่มีชื่อว่า “สรุป lifecycle”| สถานะ | เกิดอะไรขึ้น | สิ่งที่คุณทำ |
|---|---|---|
| Installing | SW กำลังถูกตั้งค่า | pre-cache asset ใน install |
| Waiting | SW ตัวใหม่พร้อมแล้วแต่ถูกถ่วงไว้ | เรียก skipWaiting() ได้ตามต้องการ |
| Activating | SW ตัวใหม่กำลังเข้ารับช่วงต่อ | ลบ cache เก่าใน activate |
| Activated | SW ควบคุมเต็มที่แล้ว | เสิร์ฟ request ผ่าน fetch event |
| Redundant | SW ถูกแทนที่หรือล้มเหลว | ไม่ต้องทำอะไร — browser จะกำจัดให้เอง |
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
| ใช้ skipWaiting() + clients.claim() | ผู้ใช้ได้ version ใหม่ทันทีโดยไม่ต้องปิด tab ทั้งหมด | เสี่ยง breaking change ถ้า version เก่ากับใหม่ไม่ compatible กันระหว่างที่หน้าเว็บยังเปิดอยู่ |
| ปล่อยให้รอ default lifecycle (waiting จนกว่า tab ทั้งหมดจะปิด) | ปลอดภัยกว่า ไม่มี version mismatch ระหว่าง client กับ server | ผู้ใช้อาจติดอยู่กับ version เก่านานถ้าไม่ปิด tab เอง |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- ลืมว่า install event ต้อง resolve สำเร็จก่อนเข้า waiting — ถ้า cache.addAll() fail ทั้ง install จะ fail เงียบๆ
- เรียก skipWaiting() โดยไม่คู่กับ versioned cache name ทำให้ client เปลี่ยนไปใช้ service worker ใหม่แต่ยังอ่าน cache เก่าที่ไม่ตรงกัน
- ไม่ทดสอบ flow “หลาย tab เปิดพร้อมกัน” ทำให้พลาด edge case ที่ worker เก่ายังคุม tab บางอันอยู่ระหว่าง transition
💡 ตัวอย่างจากของจริง
Google Docs — คุม lifecycle อย่างระมัดระวังเพื่อไม่ให้ผู้ใช้ที่กำลังแก้เอกสารอยู่โดน service worker ใหม่ตัดการทำงานกลางคัน
YouTube — ใช้ state machine ของ lifecycle ร่วมกับ UI แจ้งเตือน “มีเวอร์ชันใหม่” แทนที่จะ force reload ผู้ใช้แบบไม่บอกกล่าว