ในบางกรณี เว็บแอปอาจต้องสร้างช่องทางการสื่อสารแบบ 2 ทางระหว่างหน้าเว็บกับ Service Worker
ตัวอย่างเช่น ใน PWA ของพอดแคสต์ คุณสามารถสร้างฟีเจอร์ที่ให้ผู้ใช้ดาวน์โหลดตอนเพื่อ รับชมแบบออฟไลน์ และอนุญาตให้ Service Worker แจ้งความคืบหน้าให้หน้าเว็บทราบเป็นประจำ เพื่อให้เธรดหลักอัปเดต UI ได้
ในคู่มือนี้ เราจะมาดูวิธีต่างๆ ในการใช้การสื่อสารแบบ 2 ทางระหว่างบริบทของ Window กับ Service Worker โดยการสำรวจ API ต่างๆ, ไลบรารี Workbox รวมถึงกรณีขั้นสูงบางกรณี

การใช้ Workbox
workbox-window
คือชุดโมดูลของไลบรารี Workbox ที่มีไว้สำหรับเรียกใช้ในบริบทของหน้าต่าง คลาส Workbox
มีเมธอด messageSW()
สำหรับส่งข้อความไปยัง Service Worker ที่ลงทะเบียนของอินสแตนซ์และ
รอการตอบกลับ
โค้ดหน้าเว็บต่อไปนี้จะสร้างอินสแตนซ์ Workbox
ใหม่และส่งข้อความไปยัง Service Worker
เพื่อรับเวอร์ชันของ Service Worker
const wb = new Workbox('/sw.js');
wb.register();
const swVersion = await wb.messageSW({type: 'GET_VERSION'});
console.log('Service Worker version:', swVersion);
Service Worker จะใช้เครื่องฟังสัญญาณข้อความที่ปลายอีกด้านหนึ่ง และตอบกลับ Service Worker ที่ลงทะเบียนไว้ ดังนี้
const SW_VERSION = '1.0.0';
self.addEventListener('message', (event) => {
if (event.data.type === 'GET_VERSION') {
event.ports[0].postMessage(SW_VERSION);
}
});
ไลบรารีนี้ใช้ API ของเบราว์เซอร์ที่เราจะตรวจสอบในส่วนถัดไป นั่นคือ Message Channel แต่จะแยกรายละเอียดการติดตั้งใช้งานหลายอย่างออกไป ทำให้ใช้งานได้ง่ายขึ้น พร้อมทั้งใช้ประโยชน์จากการรองรับเบราว์เซอร์ที่หลากหลายที่ API นี้มี

การใช้ Browser API
หากไลบรารี Workbox ไม่เพียงพอต่อความต้องการของคุณ ก็มี API ระดับล่างอีกหลายรายการที่พร้อมให้ใช้งานเพื่อ ใช้การสื่อสาร"สองทาง"ระหว่างหน้าเว็บกับ Service Worker โดยมีความคล้ายคลึง และแตกต่างกันดังนี้
ความคล้ายคลึง
- ในทุกกรณี การสื่อสารจะเริ่มต้นที่ฝั่งหนึ่งผ่านอินเทอร์เฟซ
postMessage()
และรับที่อีกฝั่งโดยการติดตั้งใช้งานแฮนเดิลmessage
- ในทางปฏิบัติ API ที่พร้อมใช้งานทั้งหมดช่วยให้เราสามารถใช้กรณีการใช้งานเดียวกันได้ แต่ API บางตัว อาจช่วยลดความซับซ้อนในการพัฒนาในบางสถานการณ์
ความแตกต่าง
- ซึ่งแต่ละวิธีจะมีวิธีระบุอีกฝั่งของการสื่อสารที่แตกต่างกัน โดยบางวิธีจะใช้การอ้างอิงที่ชัดเจนไปยังบริบทอื่น ในขณะที่บางวิธีจะสื่อสารโดยอ้อมผ่านออบเจ็กต์พร็อกซีที่สร้างขึ้นในแต่ละฝั่ง
- การรองรับเบราว์เซอร์จะแตกต่างกันไป

Broadcast Channel API
Broadcast Channel API ช่วยให้มีการสื่อสารพื้นฐานระหว่างบริบทการเรียกดูผ่านออบเจ็กต์ BroadcastChannel
หากต้องการใช้ ให้แต่ละบริบทสร้างออบเจ็กต์ BroadcastChannel
ที่มีรหัสเดียวกันก่อน
แล้วส่งและรับข้อความจากออบเจ็กต์นั้น
const broadcast = new BroadcastChannel('channel-123');
ออบเจ็กต์ BroadcastChannel จะแสดงอินเทอร์เฟซ postMessage()
เพื่อส่งข้อความไปยังบริบทการฟัง
ใดก็ได้
//send message
broadcast.postMessage({ type: 'MSG_ID', });
บริบทของเบราว์เซอร์ใดก็ได้สามารถรับฟังข้อความผ่านเมธอด onmessage
ของออบเจ็กต์ BroadcastChannel
ได้
//listen to messages
broadcast.onmessage = (event) => {
if (event.data && event.data.type === 'MSG_ID') {
//process message...
}
};
ดังที่เห็น ไม่มีข้อมูลอ้างอิงที่ชัดเจนถึงบริบทใดบริบทหนึ่ง ดังนั้นจึงไม่จำเป็นต้องขอ ข้อมูลอ้างอิงก่อนไปยัง Service Worker หรือไคลเอ็นต์ใดๆ

ข้อเสียคือ ณ เวลาที่เขียนบทความนี้ API ได้รับการรองรับจาก Chrome, Firefox และ Edge แต่เบราว์เซอร์อื่นๆ เช่น Safari ยังไม่รองรับ
Client API
Client API ช่วยให้คุณได้รับ
การอ้างอิงไปยังออบเจ็กต์ WindowClient
ทั้งหมดที่แสดงแท็บที่ใช้งานอยู่ซึ่ง Service Worker ควบคุม
เนื่องจากหน้าเว็บควบคุมโดย Service Worker เดียว จึงรับฟังและส่งข้อความไปยัง
Service Worker ที่ใช้งานอยู่โดยตรงผ่านอินเทอร์เฟซ serviceWorker
//send message
navigator.serviceWorker.controller.postMessage({
type: 'MSG_ID',
});
//listen to messages
navigator.serviceWorker.onmessage = (event) => {
if (event.data && event.data.type === 'MSG_ID') {
//process response
}
};
ในทำนองเดียวกัน Service Worker จะรับฟังข้อความโดยการใช้โปรแกรมฟัง onmessage
ดังนี้
//listen to messages
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'MSG_ID') {
//Process message
}
});
หากต้องการสื่อสารกับลูกค้า บริการ Worker จะรับอาร์เรย์ของออบเจ็กต์
WindowClient
โดยการเรียกใช้เมธอดต่างๆ เช่น
Clients.matchAll()
และ
Clients.get()
จากนั้นก็สามารถpostMessage()
รายการใดก็ได้ต่อไปนี้
//Obtain an array of Window client objects
self.clients.matchAll(options).then(function (clients) {
if (clients && clients.length) {
//Respond to last focused tab
clients[0].postMessage({type: 'MSG_ID'});
}
});

Client API
เป็นตัวเลือกที่ดีในการสื่อสารกับแท็บที่ใช้งานอยู่ทั้งหมดจาก Service Worker
ได้อย่างง่ายดายในลักษณะที่ค่อนข้างตรงไปตรงมา API นี้รองรับเบราว์เซอร์หลักทั้งหมด
แต่บางวิธีอาจไม่พร้อมใช้งาน ดังนั้นโปรดตรวจสอบการรองรับของเบราว์เซอร์ก่อน
นำไปใช้ในเว็บไซต์
ช่องทางของข้อความ
Message Channel ต้องมีการกำหนดและส่งพอร์ตจากบริบทหนึ่งไปยังอีกบริบทหนึ่งเพื่อสร้างช่องทางการสื่อสารแบบ 2 ทาง
หากต้องการเริ่มต้นช่องทาง หน้าเว็บจะสร้างออบเจ็กต์ MessageChannel
และใช้เพื่อส่งพอร์ตไปยัง Service Worker ที่ลงทะเบียนไว้
หน้านี้ยังใช้ onmessage
Listener ใน
หน้าดังกล่าวเพื่อรับข้อความจากบริบทอื่นด้วย
const messageChannel = new MessageChannel();
//Init port
navigator.serviceWorker.controller.postMessage({type: 'PORT_INITIALIZATION'}, [
messageChannel.port2,
]);
//Listen to messages
messageChannel.port1.onmessage = (event) => {
// Process message
};

Service Worker จะรับพอร์ต บันทึกการอ้างอิงไปยังพอร์ต และใช้พอร์ตเพื่อส่งข้อความไปยังอีกด้านหนึ่ง
let communicationPort;
//Save reference to port
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'PORT_INITIALIZATION') {
communicationPort = event.ports[0];
}
});
//Send messages
communicationPort.postMessage({type: 'MSG_ID'});
ปัจจุบันMessageChannel
รองรับเบราว์เซอร์หลักทั้งหมด
API ขั้นสูง: การซิงค์ในเบื้องหลังและการดึงข้อมูลในเบื้องหลัง
ในคู่มือนี้ เราได้สำรวจวิธีใช้เทคนิคการสื่อสารสองทางสำหรับกรณีที่ค่อนข้างง่าย เช่น การส่งข้อความสตริงที่อธิบายการดำเนินการที่จะทำ หรือรายการ URL ที่จะแคชจากบริบทหนึ่งไปยังอีกบริบทหนึ่ง ในส่วนนี้ เราจะดู API 2 รายการเพื่อจัดการกับสถานการณ์ที่เฉพาะเจาะจง ได้แก่ การขาดการเชื่อมต่อและการดาวน์โหลดที่ใช้เวลานาน
การซิงค์ในเบื้องหลัง
แอปแชทอาจต้องการตรวจสอบว่าข้อความจะไม่สูญหายเนื่องจากการเชื่อมต่อไม่ดี Background Sync API ช่วยให้คุณ เลื่อนการดำเนินการเพื่อลองอีกครั้งเมื่อผู้ใช้มีการเชื่อมต่อที่เสถียร ซึ่งมีประโยชน์ในการตรวจสอบว่าระบบได้ส่งสิ่งที่ผู้ใช้ต้องการส่งแล้วจริง
หน้าเว็บจะลงทะเบียน sync
แทนอินเทอร์เฟซ postMessage()
ดังนี้
navigator.serviceWorker.ready.then(function (swRegistration) {
return swRegistration.sync.register('myFirstSync');
});
จากนั้น Service Worker จะรอเหตุการณ์ sync
เพื่อประมวลผลข้อความ
self.addEventListener('sync', function (event) {
if (event.tag == 'myFirstSync') {
event.waitUntil(doSomeStuff());
}
});
ฟังก์ชัน doSomeStuff()
ควรแสดงผล Promise ที่ระบุความสำเร็จ/ความล้มเหลวของสิ่งที่ฟังก์ชัน
พยายามทำ หากฟังก์ชันทำงานสำเร็จ การซิงค์จะเสร็จสมบูรณ์ หากไม่สำเร็จ ระบบจะกำหนดเวลาการซิงค์อีกครั้งเพื่อ
ลองอีกครั้ง การซิงค์ที่ลองใหม่จะรอการเชื่อมต่อและใช้ Exponential Backoff ด้วย
เมื่อดำเนินการแล้ว Service Worker จะสื่อสารกลับไปยังหน้าเว็บเพื่อ อัปเดต UI ได้โดยใช้ API การสื่อสารใดก็ได้ที่ได้ศึกษาไปก่อนหน้านี้
การค้นหาของ Google ใช้การซิงค์ข้อมูลในเบื้องหลังเพื่อเก็บคำค้นหาที่ล้มเหลวเนื่องจากการเชื่อมต่อไม่ดี และลองค้นหาอีกครั้งในภายหลังเมื่อผู้ใช้ออนไลน์ เมื่อดำเนินการแล้ว ระบบจะแจ้งผลลัพธ์ให้ผู้ใช้ทราบผ่านการแจ้งเตือนแบบพุชบนเว็บ

การดึงข้อมูลในเบื้องหลัง
สำหรับงานที่ค่อนข้างสั้น เช่น การส่งข้อความ หรือรายการ URL ที่จะแคช ตัวเลือก ที่สำรวจมาจนถึงตอนนี้เป็นตัวเลือกที่ดี หากงานใช้เวลานานเกินไป เบราว์เซอร์จะปิด Service Worker ไม่เช่นนั้นจะเป็นความเสี่ยงต่อความเป็นส่วนตัวและแบตเตอรี่ของผู้ใช้
Background Fetch API ช่วยให้คุณส่งต่อภาระงานที่ใช้เวลานานไปยัง Service Worker ได้ เช่น การดาวน์โหลดภาพยนตร์ พอดแคสต์ หรือเลเวล ของเกม
หากต้องการสื่อสารกับ Service Worker จากหน้าเว็บ ให้ใช้ backgroundFetch.fetch
แทน
postMessage()
navigator.serviceWorker.ready.then(async (swReg) => {
const bgFetch = await swReg.backgroundFetch.fetch(
'my-fetch',
['/ep-5.mp3', 'ep-5-artwork.jpg'],
{
title: 'Episode 5: Interesting things.',
icons: [
{
sizes: '300x300',
src: '/ep-5-icon.png',
type: 'image/png',
},
],
downloadTotal: 60 * 1024 * 1024,
},
);
});
ออบเจ็กต์ BackgroundFetchRegistration
ช่วยให้หน้าเว็บรับฟังเหตุการณ์ progress
เพื่อติดตาม
ความคืบหน้าของการดาวน์โหลดได้
bgFetch.addEventListener('progress', () => {
// If we didn't provide a total, we can't provide a %.
if (!bgFetch.downloadTotal) return;
const percent = Math.round(
(bgFetch.downloaded / bgFetch.downloadTotal) * 100,
);
console.log(`Download progress: ${percent}%`);
});

ขั้นตอนถัดไป
ในคู่มือนี้ เราได้สำรวจกรณีทั่วไปที่สุดของการสื่อสารระหว่างหน้าเว็บและ Service Worker (การสื่อสารแบบ 2 ทาง)
หลายครั้งที่อาจต้องใช้เพียงบริบทเดียวในการสื่อสารกับอีกบริบทหนึ่งโดยไม่ต้องรับ การตอบกลับ ดูคำแนะนำเกี่ยวกับวิธีใช้เทคนิคแบบทิศทางเดียวในหน้าเว็บจากและไปยัง Service Worker พร้อมกรณีการใช้งานและตัวอย่างการใช้งานจริงได้ในคำแนะนำต่อไปนี้
- คำแนะนำในการแคชแบบบังคับ: การเรียกใช้ Service Worker จากหน้าเว็บเพื่อ แคชทรัพยากรล่วงหน้า (เช่น ในสถานการณ์การดึงข้อมูลล่วงหน้า)
- การอัปเดตการออกอากาศ: การเรียกหน้าเว็บจาก Service Worker เพื่อแจ้ง เกี่ยวกับการอัปเดตที่สำคัญ (เช่น มีเว็บแอปเวอร์ชันใหม่)