Trong một số trường hợp, ứng dụng web có thể cần thiết lập kênh giao tiếp hai chiều giữa trang và worker dịch vụ.
Ví dụ: trong một PWA podcast, bạn có thể tạo một tính năng cho phép người dùng tải các tập xuống để sử dụng khi không có mạng và cho phép worker dịch vụ thường xuyên thông báo cho trang về tiến trình để luồng chính có thể cập nhật giao diện người dùng.
Trong hướng dẫn này, chúng ta sẽ tìm hiểu các cách triển khai hoạt động giao tiếp hai chiều giữa bối cảnh Cửa sổ và trình thực thi dịch vụ, bằng cách khám phá nhiều API, thư viện Hộp công việc, cũng như một số trường hợp nâng cao.
Sử dụng Workbox
workbox-window
là một tập hợp mô-đun của Thư viện hộp làm việc dự kiến chạy trong ngữ cảnh cửa sổ. Lớp Workbox
cung cấp một phương thức messageSW()
để gửi thông báo đến worker dịch vụ đã đăng ký của thực thể và chờ phản hồi.
Mã trang sau đây tạo một thực thể Workbox
mới và gửi thông báo đến worker dịch vụ để lấy phiên bản của worker đó:
const wb = new Workbox('/sw.js');
wb.register();
const swVersion = await wb.messageSW({type: 'GET_VERSION'});
console.log('Service Worker version:', swVersion);
Trình chạy dịch vụ triển khai trình nghe thông báo ở đầu bên kia và phản hồi trình chạy dịch vụ đã đăng ký:
const SW_VERSION = '1.0.0';
self.addEventListener('message', (event) => {
if (event.data.type === 'GET_VERSION') {
event.ports[0].postMessage(SW_VERSION);
}
});
Về cơ bản, thư viện này sử dụng một API trình duyệt mà chúng ta sẽ xem xét trong phần tiếp theo: Kênh thông báo, nhưng tóm tắt nhiều chi tiết triển khai, giúp dễ sử dụng hơn, đồng thời tận dụng khả năng hỗ trợ trình duyệt rộng rãi mà API này có.
Sử dụng API trình duyệt
Nếu thư viện Workbox không đủ đáp ứng nhu cầu của bạn, bạn có thể sử dụng một số API cấp thấp hơn để triển khai giao tiếp "hai chiều" giữa các trang và worker dịch vụ. Chúng có một số điểm tương đồng và khác biệt:
Điểm tương đồng:
- Trong mọi trường hợp, quá trình giao tiếp bắt đầu ở một đầu qua giao diện
postMessage()
và được nhận ở đầu kia bằng cách triển khai trình xử lýmessage
. - Trên thực tế, tất cả API có sẵn đều cho phép chúng tôi triển khai cùng một trường hợp sử dụng, nhưng một vài API trong số đó có thể đơn giản hoá quá trình phát triển trong một số trường hợp.
Điểm khác biệt:
- Các phương thức này có nhiều cách xác định phía bên kia của quá trình giao tiếp: một số phương thức sử dụng tham chiếu rõ ràng đến ngữ cảnh khác, trong khi một số phương thức khác có thể giao tiếp ngầm thông qua một đối tượng proxy được tạo bản sao ở mỗi bên.
- Khả năng hỗ trợ trình duyệt sẽ khác nhau giữa các trình duyệt.
Broadcast Channel API
Broadcast Channel API cho phép giao tiếp cơ bản giữa các ngữ cảnh duyệt web thông qua các đối tượng BroadcastChannel.
Để triển khai lớp này, trước tiên, mỗi ngữ cảnh phải tạo thực thể cho đối tượng BroadcastChannel
với cùng một mã nhận dạng, đồng thời gửi và nhận thông báo qua đối tượng đó:
const broadcast = new BroadcastChannel('channel-123');
Đối tượng BroadcastChannel hiển thị giao diện postMessage()
để gửi thông báo đến bất kỳ ngữ cảnh nghe nào:
//send message
broadcast.postMessage({ type: 'MSG_ID', });
Mọi ngữ cảnh của trình duyệt đều có thể theo dõi thông báo thông qua phương thức onmessage
của đối tượng BroadcastChannel
:
//listen to messages
broadcast.onmessage = (event) => {
if (event.data && event.data.type === 'MSG_ID') {
//process message...
}
};
Như đã thấy, không có tham chiếu rõ ràng đến một ngữ cảnh cụ thể, vì vậy, trước tiên, bạn không cần phải lấy tham chiếu đến worker dịch vụ hoặc bất kỳ ứng dụng cụ thể nào.
Nhược điểm là tại thời điểm viết bài này, API có sự hỗ trợ của Chrome, Firefox và Edge, nhưng các trình duyệt khác như Safari chưa hỗ trợ API này.
API ứng dụng
API ứng dụng cho phép bạn lấy tham chiếu đến tất cả các đối tượng WindowClient
đại diện cho các thẻ đang hoạt động mà worker dịch vụ đang kiểm soát.
Vì trang do một trình chạy dịch vụ kiểm soát, nên trang sẽ lắng nghe và gửi thông báo trực tiếp đến trình chạy dịch vụ đang hoạt động thông qua giao diện 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
}
};
Tương tự, worker dịch vụ sẽ theo dõi thông báo bằng cách triển khai trình nghe onmessage
:
//listen to messages
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'MSG_ID') {
//Process message
}
});
Để giao tiếp lại với bất kỳ ứng dụng nào, worker dịch vụ sẽ lấy một mảng các đối tượng WindowClient
bằng cách thực thi các phương thức như Clients.matchAll()
và Clients.get()
. Sau đó, nó có thể postMessage()
bất kỳ thành phần nào trong số đó:
//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
là một lựa chọn phù hợp để dễ dàng giao tiếp với tất cả các thẻ đang hoạt động từ một worker dịch vụ theo cách tương đối đơn giản. API này được tất cả trình duyệt lớn hỗ trợ, nhưng có thể không phải phương thức nào cũng hoạt động. Vì vậy, hãy nhớ kiểm tra khả năng hỗ trợ trình duyệt trước khi triển khai API này trong trang web của bạn.
Kênh tin nhắn
Kênh thông báo yêu cầu xác định và truyền cổng từ ngữ cảnh này sang ngữ cảnh khác để thiết lập kênh giao tiếp hai chiều.
Để khởi chạy kênh, trang sẽ tạo bản sao của đối tượng MessageChannel
và sử dụng đối tượng đó để gửi cổng đến worker dịch vụ đã đăng ký. Trang này cũng triển khai trình nghe onmessage
trên trang này để nhận thông báo từ ngữ cảnh khác:
const messageChannel = new MessageChannel();
//Init port
navigator.serviceWorker.controller.postMessage({type: 'PORT_INITIALIZATION'}, [
messageChannel.port2,
]);
//Listen to messages
messageChannel.port1.onmessage = (event) => {
// Process message
};
Worker dịch vụ nhận cổng, lưu tệp tham chiếu đến cổng đó và sử dụng cổng đó để gửi thông báo đến bên kia:
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
hiện được tất cả trình duyệt chính hỗ trợ.
API nâng cao: Đồng bộ hoá ở chế độ nền và Tìm nạp ở chế độ nền
Trong hướng dẫn này, chúng tôi đã tìm hiểu các cách triển khai kỹ thuật giao tiếp hai chiều cho các trường hợp tương đối đơn giản, chẳng hạn như truyền một thông báo chuỗi mô tả thao tác cần thực hiện hoặc danh sách URL để lưu vào bộ nhớ đệm từ ngữ cảnh này sang ngữ cảnh khác. Trong phần này, chúng ta sẽ khám phá hai API để xử lý các trường hợp cụ thể: thiếu kết nối và tải xuống lâu.
Đồng bộ hoá ở chế độ nền
Ứng dụng trò chuyện có thể muốn đảm bảo rằng tin nhắn không bao giờ bị mất do kết nối kém. Background Sync API (API Đồng bộ hoá ở chế độ nền) cho phép bạn trì hoãn các thao tác để thử lại khi người dùng có kết nối ổn định. Điều này rất hữu ích trong việc đảm bảo rằng bất kỳ nội dung nào người dùng muốn gửi đều thực sự được gửi.
Thay vì giao diện postMessage()
, trang này sẽ đăng ký một sync
:
navigator.serviceWorker.ready.then(function (swRegistration) {
return swRegistration.sync.register('myFirstSync');
});
Sau đó, worker dịch vụ sẽ theo dõi sự kiện sync
để xử lý thông báo:
self.addEventListener('sync', function (event) {
if (event.tag == 'myFirstSync') {
event.waitUntil(doSomeStuff());
}
});
Hàm doSomeStuff()
sẽ trả về một lời hứa cho biết kết quả thành công/không thành công của bất kỳ việc gì mà hàm đang cố gắng thực hiện. Nếu lời hứa được thực hiện, quá trình đồng bộ hoá sẽ hoàn tất. Nếu không thành công, một lần đồng bộ hoá khác sẽ được lên lịch để thử lại. Các lần đồng bộ hoá thử lại cũng chờ kết nối và sử dụng thời gian đợi luỹ thừa.
Sau khi thực hiện thao tác, worker dịch vụ có thể giao tiếp lại với trang để cập nhật giao diện người dùng bằng cách sử dụng bất kỳ API giao tiếp nào đã khám phá trước đó.
Google Tìm kiếm sử dụng tính năng Đồng bộ hoá ở chế độ nền để duy trì các truy vấn không thành công do kết nối kém và thử lại các truy vấn đó sau khi người dùng có kết nối mạng. Sau khi thực hiện thao tác, các dịch vụ này sẽ thông báo kết quả cho người dùng thông qua thông báo đẩy web:
Tìm nạp ở chế độ nền
Đối với các công việc tương đối ngắn như gửi tin nhắn hoặc danh sách URL để lưu vào bộ nhớ đệm, các tuỳ chọn đã khám phá cho đến nay là một lựa chọn phù hợp. Nếu tác vụ mất quá nhiều thời gian, trình duyệt sẽ chấm dứt worker dịch vụ, nếu không, tác vụ này sẽ gây rủi ro cho quyền riêng tư và pin của người dùng.
Background Fetch API (API Tìm nạp ở chế độ nền) cho phép bạn giảm tải một tác vụ dài cho một worker dịch vụ, chẳng hạn như tải phim, podcast hoặc cấp độ của trò chơi xuống.
Để giao tiếp với trình chạy dịch vụ từ trang, hãy sử dụng backgroundFetch.fetch
thay vì 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,
},
);
});
Đối tượng BackgroundFetchRegistration
cho phép trang nghe sự kiện progress
để theo dõi tiến trình tải xuống:
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}%`);
});
Các bước tiếp theo
Trong hướng dẫn này, chúng tôi đã tìm hiểu trường hợp giao tiếp chung nhất giữa trình chạy trang và trình chạy dịch vụ (giao tiếp hai chiều).
Nhiều khi, một thành phần có thể chỉ cần một ngữ cảnh để giao tiếp với thành phần khác mà không cần nhận phản hồi. Hãy xem các hướng dẫn sau đây để biết cách triển khai các kỹ thuật một chiều trong trang của bạn từ và đến worker dịch vụ, cùng với các trường hợp sử dụng và ví dụ về sản xuất:
- Hướng dẫn bắt buộc về việc lưu vào bộ nhớ đệm: Gọi trình chạy dịch vụ từ trang để lưu trước tài nguyên vào bộ nhớ đệm (ví dụ: trong các trường hợp tải trước).
- Cập nhật thông báo: Gọi trang từ worker dịch vụ để thông báo về các bản cập nhật quan trọng (ví dụ: có phiên bản mới của ứng dụng web).