แง่มุมที่สำคัญของ Progressive Web App คือความน่าเชื่อถือ โดยสามารถโหลดชิ้นงานได้อย่างรวดเร็ว ทำให้ผู้ใช้มีส่วนร่วมและให้ความคิดเห็นได้ทันที แม้ว่าเครือข่ายจะไม่เสถียรก็ตาม ทำไมถึงเป็นเช่นนั้น ขอบคุณเหตุการณ์ fetch
ของ Service Worker
เหตุการณ์การดึงข้อมูล
เหตุการณ์ fetch
ช่วยให้เราสกัดกั้นคำขอเครือข่ายทุกรายการที่ PWA สร้างขึ้นในขอบเขตของ Service Worker ได้ ทั้งสำหรับคำขอที่มีต้นทางเดียวกันและคำขอข้ามต้นทาง นอกเหนือจากคำขอการนำทางและคำขอเนื้อหาแล้ว การดึงข้อมูลจาก Service Worker ที่ติดตั้งยังช่วยให้การเข้าชมหน้าเว็บหลังจากโหลดเว็บไซต์ครั้งแรกแสดงผลได้โดยไม่ต้องเรียกใช้เครือข่าย
แฮนเดิลเลอร์ fetch
จะรับคำขอทั้งหมดจากแอป ซึ่งรวมถึง URL และส่วนหัวของ HTTP และช่วยให้นักพัฒนาแอปตัดสินใจได้ว่าจะประมวลผลคำขอเหล่านั้นอย่างไร
Service Worker สามารถส่งต่อคำขอไปยังเครือข่าย ตอบกลับด้วยการตอบกลับที่แคชไว้ก่อนหน้านี้ หรือสร้างการตอบกลับใหม่ คุณเลือกได้เลย ตัวอย่างง่ายๆ มีดังนี้
self.addEventListener("fetch", event => {
console.log(`URL requested: ${event.request.url}`);
});
การตอบกลับคำขอ
เมื่อคำขอเข้ามาใน Service Worker คุณจะทำได้ 2 อย่างคือ ไม่สนใจคำขอ ซึ่งจะทำให้คำขอไปที่เครือข่าย หรือตอบกลับคำขอ การตอบกลับคำขอจากภายใน Service Worker คือวิธีที่คุณเลือกได้ว่าจะส่งอะไรและส่งกลับไปยัง PWA อย่างไร แม้ว่าผู้ใช้จะออฟไลน์อยู่ก็ตาม
หากต้องการตอบกลับคำขอขาเข้า ให้เรียกใช้ event.respondWith()
จากภายในตัวแฮนเดิลเหตุการณ์ fetch
ดังนี้
// fetch event handler in your service worker file
self.addEventListener("fetch", event => {
const response = .... // a response or a Promise of response
event.respondWith(response);
});
คุณต้องเรียกใช้ respondWith()
แบบพร้อมกันและต้องส่งคืนออบเจ็กต์ Response แต่คุณจะเรียกใช้ respondWith()
ไม่ได้หลังจากที่ตัวแฮนเดิลเหตุการณ์การดึงข้อมูลเสร็จสิ้นแล้ว เช่น ภายในฟังก์ชันเรียกแบบไม่พร้อมกัน หากต้องการรอการตอบกลับที่สมบูรณ์ คุณสามารถส่ง Promise ไปยัง respondWith()
ที่จะแก้ไขด้วย Response
การสร้างคำตอบ
Fetch API ช่วยให้คุณสร้างการตอบกลับ HTTP ในโค้ด JavaScript ได้ และแคชการตอบกลับเหล่านั้นได้โดยใช้ Cache Storage API และส่งกลับราวกับว่ามาจากการตอบกลับจากเว็บเซิร์ฟเวอร์
หากต้องการสร้างการตอบกลับ ให้สร้างออบเจ็กต์ Response
ใหม่ โดยตั้งค่าเนื้อหาและตัวเลือกต่างๆ เช่น สถานะและส่วนหัว
const simpleResponse = new Response("Body of the HTTP response");
const options = {
status: 200,
headers: {
'Content-type': 'text/html'
}
};
const htmlResponse = new Response("<b>HTML</b> content", options)
การตอบกลับจากแคช
ตอนนี้คุณทราบวิธีแสดงการตอบกลับ HTTP จาก Service Worker แล้ว ถึงเวลาใช้อินเทอร์เฟซพื้นที่เก็บข้อมูลแคชเพื่อจัดเก็บชิ้นงานในอุปกรณ์แล้ว
คุณสามารถใช้ Cache Storage API เพื่อตรวจสอบว่าคำขอที่ได้รับจาก PWA พร้อมใช้งานในแคชหรือไม่ และหากพร้อมใช้งาน ให้ตอบกลับ respondWith()
ด้วยคำขอดังกล่าว
โดยคุณต้องค้นหาภายในแคชก่อน ฟังก์ชัน match()
ซึ่งมีให้ใช้งานในcaches
อินเทอร์เฟซระดับบนสุดจะค้นหาร้านค้าทั้งหมดในต้นทางหรือในออบเจ็กต์แคชที่เปิดอยู่รายการเดียว
match()
ฟังก์ชันจะรับคำขอ HTTP หรือ URL เป็นอาร์กิวเมนต์ และแสดงผล Promise ที่แก้ไขด้วยการตอบกลับที่เชื่อมโยงกับคีย์ที่เกี่ยวข้อง
// Global search on all caches in the current origin
caches.match(urlOrRequest).then(response => {
console.log(response ? response : "It's not in the cache");
});
// Cache-specific search
caches.open("pwa-assets").then(cache => {
cache.match(urlOrRequest).then(response => {
console.log(response ? response : "It's not in the cache");
});
});
กลยุทธ์การแคช
การแสดงไฟล์จากแคชของเบราว์เซอร์เท่านั้นไม่เหมาะกับทุกกรณีการใช้งาน เช่น ผู้ใช้หรือเบราว์เซอร์สามารถนำแคชออกได้ ด้วยเหตุนี้ คุณจึงควรกำหนดกลยุทธ์ของคุณเองในการแสดงชิ้นงานสำหรับ PWA
คุณไม่จำเป็นต้องใช้กลยุทธ์การแคชเพียงอย่างเดียว คุณกำหนดค่าที่แตกต่างกันสำหรับรูปแบบ URL ที่แตกต่างกันได้ เช่น คุณอาจมีกลยุทธ์หนึ่งสำหรับชิ้นงาน UI ขั้นต่ำ อีกกลยุทธ์หนึ่งสำหรับการเรียก API และอีกกลยุทธ์หนึ่งสำหรับ URL ของรูปภาพและข้อมูล
หากต้องการทำเช่นนี้ ให้อ่านevent.request.url
ในServiceWorkerGlobalScope.onfetch
แล้วแยกวิเคราะห์ผ่านนิพจน์ทั่วไปหรือรูปแบบ URL (ขณะที่เขียนบทความนี้ บางแพลตฟอร์มยังไม่รองรับรูปแบบ URL)
กลยุทธ์ที่พบบ่อยที่สุดมีดังนี้
- แคชก่อน
- ค้นหาการตอบกลับที่แคชไว้ก่อน แล้วจะกลับไปใช้เครือข่ายหากไม่พบ
- เครือข่ายแรก
- ขอการตอบกลับจากเครือข่ายก่อน หากไม่ได้รับการตอบกลับ ระบบจะตรวจสอบการตอบกลับในแคช
- แสดงขณะที่ไม่มีอัปเดตและตรวจสอบความถูกต้องใหม่
- แสดงการตอบกลับจากแคช ขณะที่ในเบื้องหลังจะขอเวอร์ชันล่าสุดและบันทึกลงในแคชสำหรับการขอเนื้อหาครั้งถัดไป
- เครือข่ายเท่านั้น
- ตอบกลับด้วยการตอบสนองจากเครือข่ายหรือข้อผิดพลาดเสมอ ระบบจะไม่ตรวจสอบแคช
- แคชเท่านั้น
- ตอบกลับด้วยการตอบกลับจากแคชหรือแสดงข้อผิดพลาดเสมอ ระบบจะไม่ปรึกษาเครือข่าย คุณต้องเพิ่มชิ้นงานที่จะแสดงโดยใช้กลยุทธ์นี้ลงในแคชก่อนที่จะมีการส่งคำขอ
แคชก่อน
เมื่อใช้กลยุทธ์นี้ Service Worker จะค้นหาคำขอที่ตรงกันในแคชและแสดงผลการตอบกลับที่เกี่ยวข้องหากมีการแคชไว้ หรือดึงข้อมูลการตอบกลับจากเครือข่าย (อัปเดตแคชสำหรับการเรียกในอนาคตหรือไม่ก็ได้) หากไม่มีทั้งการตอบกลับจากแคชและการตอบกลับจากเครือข่าย คำขอจะเกิดข้อผิดพลาด เนื่องจากการแสดงชิ้นงานโดยไม่ต้องไปที่เครือข่ายมักจะเร็วกว่า กลยุทธ์นี้จึงให้ความสำคัญกับประสิทธิภาพมากกว่าความใหม่
self.addEventListener("fetch", event => {
event.respondWith(
caches.match(event.request)
.then(cachedResponse => {
// It can update the cache to serve updated content on the next request
return cachedResponse || fetch(event.request);
}
)
)
});
เครือข่ายเป็นอันดับแรก
กลยุทธ์นี้เป็นภาพสะท้อนของกลยุทธ์แคชก่อน โดยจะตรวจสอบว่าคำขอสามารถดำเนินการจากเครือข่ายได้หรือไม่ และหากทำไม่ได้ ก็จะพยายามเรียกข้อมูลจากแคช เช่น แคชก่อน หากไม่มีทั้งการตอบสนองของเครือข่ายและการตอบสนองของแคช คำขอจะแสดงข้อผิดพลาด โดยปกติแล้วการรับการตอบกลับจากเครือข่ายจะช้ากว่าการรับจากแคช กลยุทธ์นี้จึงให้ความสำคัญกับเนื้อหาที่อัปเดตมากกว่าประสิทธิภาพ
self.addEventListener("fetch", event => {
event.respondWith(
fetch(event.request)
.catch(error => {
return caches.match(event.request) ;
})
);
});
แสดงขณะที่ไม่มีอัปเดต
กลยุทธ์ "ล้าสมัยขณะตรวจสอบซ้ำ" จะแสดงการตอบกลับที่แคชไว้ทันที จากนั้นจะตรวจสอบการอัปเดตในเครือข่าย และแทนที่การตอบกลับที่แคชไว้หากพบ กลยุทธ์นี้จะส่งคำขอเครือข่ายเสมอ เนื่องจากแม้ว่าจะพบทรัพยากรที่แคชไว้ แต่ก็จะพยายามอัปเดตสิ่งที่อยู่ในแคชด้วยสิ่งที่ได้รับจากเครือข่าย เพื่อใช้เวอร์ชันที่อัปเดตแล้วในคำขอถัดไป ดังนั้น กลยุทธ์นี้จึงเป็นวิธีที่ช่วยให้คุณได้รับประโยชน์จากการแสดงผลอย่างรวดเร็วของกลยุทธ์แคชก่อน และอัปเดตแคชในเบื้องหลัง
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(cachedResponse => {
const networkFetch = fetch(event.request).then(response => {
// update the cache with a clone of the network response
const responseClone = response.clone()
caches.open(url.searchParams.get('name')).then(cache => {
cache.put(event.request, responseClone)
})
return response
}).catch(function (reason) {
console.error('ServiceWorker fetch failed: ', reason)
})
// prioritize cached response over network
return cachedResponse || networkFetch
}
)
)
})
เครือข่ายเท่านั้น
กลยุทธ์เครือข่ายเท่านั้นจะคล้ายกับลักษณะการทำงานของเบราว์เซอร์ที่ไม่มี Service Worker หรือ Cache Storage API คำขอจะแสดงทรัพยากรก็ต่อเมื่อดึงข้อมูลจากเครือข่ายได้เท่านั้น ซึ่งมักจะมีประโยชน์สำหรับทรัพยากรต่างๆ เช่น คำขอ API แบบออนไลน์เท่านั้น
แคชเท่านั้น
กลยุทธ์แคชเท่านั้นช่วยให้มั่นใจได้ว่าคำขอจะไม่ไปที่เครือข่าย และระบบจะตอบสนองต่อคำขอขาเข้าทั้งหมดด้วยรายการแคชที่สร้างไว้ล่วงหน้า โค้ดต่อไปนี้ใช้ตัวแฮนเดิลเหตุการณ์ fetch
กับเมธอด match
ของที่เก็บแคชเพื่อตอบสนองแคชเท่านั้น
self.addEventListener("fetch", event => {
event.respondWith(caches.match(event.request));
});
กลยุทธ์ที่กำหนดเอง
แม้ว่ากลยุทธ์การแคชข้างต้นจะเป็นกลยุทธ์ทั่วไป แต่คุณก็ยังคงเป็นผู้ควบคุม Service Worker และวิธีจัดการคำขอ หากไม่มีตัวเลือกใดที่ตรงกับความต้องการ ให้สร้างตัวเลือกของคุณเอง
เช่น คุณสามารถใช้กลยุทธ์เครือข่ายก่อนโดยมีระยะหมดเวลาเพื่อจัดลําดับความสําคัญของเนื้อหาที่อัปเดตแล้วได้ แต่จะทําได้ก็ต่อเมื่อการตอบสนองปรากฏภายในเกณฑ์ที่คุณตั้งไว้เท่านั้น นอกจากนี้ คุณยังผสานการตอบกลับที่แคชไว้กับการตอบกลับจากเครือข่าย และสร้างการตอบกลับที่ซับซ้อนจาก Service Worker ได้ด้วย
การอัปเดตชิ้นงาน
การอัปเดตชิ้นงานที่แคชไว้ของ PWA อาจเป็นเรื่องท้าทาย แม้ว่ากลยุทธ์ "ล้าสมัยขณะตรวจสอบซ้ำ" จะเป็นวิธีหนึ่งในการดำเนินการดังกล่าว แต่ก็ไม่ใช่เพียงวิธีเดียว ในบทการอัปเดต คุณจะได้เรียนรู้เทคนิคต่างๆ ในการอัปเดตเนื้อหาและชิ้นงานของแอป