สิ่งสำคัญของ Progressive Web Apps คือความน่าเชื่อถือ สามารถโหลดชิ้นงานได้อย่างรวดเร็ว ดึงดูดผู้ใช้ให้มีส่วนร่วมอยู่เสมอ และแสดงความคิดเห็นได้ทันที แม้ในสภาวะเครือข่ายที่ไม่ดี เป็นไปได้อย่างไร ขอขอบคุณเหตุการณ์ 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 แล้ว ก็ถึงเวลาใช้อินเทอร์เฟซพื้นที่เก็บข้อมูลแคชเพื่อจัดเก็บชิ้นงานในอุปกรณ์
คุณสามารถใช้แคชสตอเรจ API เพื่อตรวจสอบว่าคําขอที่ได้รับจาก PWA มีอยู่ในแคชหรือไม่ หากมี ให้ตอบกลับ respondWith()
ด้วยคําขอนั้น
โดยคุณต้องค้นหาภายในแคชก่อน ฟังก์ชัน match()
ซึ่งมีอยู่ในอินเทอร์เฟซ caches
ระดับบนสุดจะค้นหาร้านค้าทั้งหมดในต้นทาง หรือในออบเจ็กต์แคชที่เปิดอยู่รายการเดียว
ฟังก์ชัน match()
จะรับคําขอ HTTP หรือ URL เป็นอาร์กิวเมนต์ และแสดงผลพรอมต์ที่แก้ไขด้วยการตอบกลับที่เชื่อมโยงกับคีย์ที่เกี่ยวข้อง
// 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
คุณใช้กลยุทธ์การแคชได้มากกว่า 1 รายการ คุณกำหนด URL ที่แตกต่างกันสำหรับรูปแบบ URL ที่แตกต่างกันได้ เช่น คุณอาจมีกลยุทธ์ 1 รายการสําหรับชิ้นงาน UI ขั้นต่ำ กลยุทธ์อื่นสําหรับการเรียก API และกลยุทธ์ที่ 3 สําหรับ 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 เป็นเวอร์ชันล่าสุดอยู่เสมออาจเป็นเรื่องท้าทาย แม้ว่ากลยุทธ์ "stale while revalidate" จะเป็นวิธีหนึ่งในการดำเนินการ แต่ก็ไม่ได้เป็นวิธีเดียว ในบทอัปเดต คุณจะได้เรียนรู้เทคนิคต่างๆ ในการดูแลรักษาเนื้อหาและชิ้นงานของแอปให้ทันสมัยอยู่เสมอ