Cách suy nghĩ khi nghĩ về worker dịch vụ.
Worker dịch vụ rất mạnh mẽ và hoàn toàn đáng để tìm hiểu. Các tính năng này giúp bạn mang đến cho người dùng một trải nghiệm hoàn toàn mới. Trang web của bạn có thể tải ngay lập tức. Ứng dụng này có thể hoạt động ngoại tuyến. Bạn có thể cài đặt ứng dụng này dưới dạng một ứng dụng dành riêng cho nền tảng và cảm nhận được sự tinh tế của ứng dụng này, nhưng vẫn có phạm vi tiếp cận và sự tự do của web.
Tuy nhiên, worker dịch vụ không giống như bất kỳ thứ gì mà hầu hết các nhà phát triển web chúng ta đã quen. Các công cụ này có độ dốc học tập cao và một số vấn đề bạn cần lưu ý.
Gần đây, Nhà phát triển Google và tôi đã cộng tác trong một dự án — Service Workies — một trò chơi miễn phí để tìm hiểu về trình chạy dịch vụ. Trong quá trình xây dựng và xử lý các thông tin phức tạp về worker dịch vụ, tôi gặp phải một số vấn đề. Điều giúp tôi nhiều nhất là việc đưa ra một số phép ẩn dụ mô tả. Trong bài đăng này, chúng ta sẽ khám phá các mô hình tinh thần này và tìm hiểu những đặc điểm nghịch lý khiến worker trở nên vừa khó hiểu vừa tuyệt vời.
Giống nhau nhưng khác
Trong khi lập trình worker dịch vụ, bạn sẽ thấy nhiều thứ quen thuộc. Bạn có thể sử dụng các tính năng mới yêu thích của ngôn ngữ JavaScript. Bạn theo dõi các sự kiện trong vòng đời giống như với các sự kiện trên giao diện người dùng. Bạn quản lý luồng điều khiển bằng các lời hứa như bình thường.
Nhưng hành vi của các worker dịch vụ khác khiến bạn phải vò đầu bức tai. Đặc biệt là khi bạn làm mới trang và không thấy các thay đổi về mã được áp dụng.
Một lớp mới
Thông thường, khi xây dựng một trang web, bạn chỉ cần quan tâm đến hai lớp: ứng dụng và máy chủ. Trình chạy dịch vụ là một lớp hoàn toàn mới nằm ở giữa.
Hãy coi trình chạy dịch vụ là một loại tiện ích trình duyệt mà trang web của bạn có thể cài đặt trong trình duyệt của người dùng. Sau khi được cài đặt, trình chạy dịch vụ sẽ mở rộng trình duyệt cho trang web của bạn bằng một lớp trung gian mạnh mẽ. Lớp worker dịch vụ này có thể chặn và xử lý tất cả các yêu cầu mà trang web của bạn đưa ra.
Lớp worker dịch vụ có vòng đời riêng độc lập với thẻ trình duyệt. Việc làm mới trang đơn giản là không đủ để cập nhật trình chạy dịch vụ, giống như việc bạn không mong đợi việc làm mới trang sẽ cập nhật mã được triển khai trên máy chủ. Mỗi lớp có các quy tắc riêng để cập nhật.
Trong trò chơi Service Workies, chúng tôi sẽ trình bày nhiều thông tin chi tiết về vòng đời của worker dịch vụ và cung cấp cho bạn nhiều bài tập thực hành về worker dịch vụ.
Mạnh mẽ nhưng có giới hạn
Việc có trình chạy dịch vụ trên trang web sẽ mang lại cho bạn nhiều lợi ích đáng kinh ngạc. Trang web của bạn có thể:
- hoạt động hoàn hảo ngay cả khi người dùng không có kết nối mạng
- cải thiện đáng kể hiệu suất thông qua tính năng lưu vào bộ nhớ đệm
- sử dụng thông báo đẩy
- được cài đặt dưới dạng PWA
Mặc dù có thể làm được nhiều việc, nhưng worker dịch vụ vẫn bị giới hạn về thiết kế. Các trang web này không thể làm gì đồng bộ hoặc trong cùng một luồng với trang web của bạn. Điều đó có nghĩa là bạn sẽ không có quyền truy cập vào:
- localStorage
- DOM
- cửa sổ
Tin vui là có một số cách để trang của bạn có thể giao tiếp với worker dịch vụ, bao gồm postMessage
trực tiếp, Kênh thông báo một với một và Kênh truyền tin một với nhiều.
Có thời gian tồn tại dài nhưng ngắn
Một worker dịch vụ đang hoạt động sẽ tiếp tục hoạt động ngay cả sau khi người dùng rời khỏi trang web của bạn hoặc đóng thẻ. Trình duyệt sẽ giữ lại trình chạy dịch vụ này để sẵn sàng cho lần tiếp theo người dùng quay lại trang web của bạn. Trước khi yêu cầu đầu tiên được thực hiện, trình chạy dịch vụ sẽ có cơ hội chặn yêu cầu đó và kiểm soát trang. Đây là yếu tố cho phép trang web hoạt động khi không có mạng – trình chạy dịch vụ có thể phân phát phiên bản đã lưu vào bộ nhớ đệm của chính trang đó, ngay cả khi người dùng không có kết nối Internet.
Trong Service Workies, chúng ta minh hoạ khái niệm này bằng Kolohe (một worker dịch vụ thân thiện) chặn và xử lý các yêu cầu.
Đã dừng
Mặc dù có vẻ như không bao giờ ngừng hoạt động, nhưng bạn có thể dừng worker dịch vụ bất cứ lúc nào. Trình duyệt không muốn lãng phí tài nguyên cho một trình chạy dịch vụ hiện không làm gì cả. Việc bị dừng không giống với việc bị chấm dứt – trình chạy dịch vụ vẫn được cài đặt và kích hoạt. Thiết bị chỉ được chuyển sang chế độ ngủ. Vào lần tiếp theo cần đến (ví dụ: để xử lý một yêu cầu), trình duyệt sẽ đánh thức lại trình bổ trợ.
waitUntil
Vì có thể liên tục bị đưa vào trạng thái ngủ, nên trình chạy dịch vụ của bạn cần có cách để cho trình duyệt biết khi nào trình chạy dịch vụ đang làm việc gì đó quan trọng và không muốn ngủ trưa. Đây là lúc event.waitUntil()
phát huy tác dụng. Phương thức này mở rộng vòng đời mà nó được sử dụng, giúp vòng đời không bị dừng và không chuyển sang giai đoạn tiếp theo của vòng đời cho đến khi chúng ta sẵn sàng. Điều này giúp chúng ta có thời gian thiết lập bộ nhớ đệm, tìm nạp tài nguyên từ mạng, v.v.
Ví dụ này cho trình duyệt biết rằng trình chạy dịch vụ của chúng ta chưa cài đặt xong cho đến khi bộ nhớ đệm assets
được tạo và điền sẵn hình ảnh một thanh kiếm:
self.addEventListener("install", event => {
event.waitUntil(
caches.open("assets").then(cache => {
return cache.addAll(["/weapons/sword/blade.png"]);
})
);
});
Cẩn thận với trạng thái toàn cục
Khi quá trình khởi động/dừng này xảy ra, phạm vi toàn cục của worker dịch vụ sẽ được đặt lại. Vì vậy, hãy cẩn thận không sử dụng bất kỳ trạng thái toàn cục nào trong worker dịch vụ, nếu không bạn sẽ thấy thất vọng vào lần tiếp theo worker dịch vụ đó thức dậy và có trạng thái khác với dự kiến.
Hãy xem xét ví dụ sau đây sử dụng trạng thái toàn cục:
const favoriteNumber = Math.random();
let hasHandledARequest = false;
self.addEventListener("fetch", event => {
console.log(favoriteNumber);
console.log(hasHandledARequest);
hasHandledARequest = true;
});
Trên mỗi yêu cầu, worker dịch vụ này sẽ ghi lại một số, giả sử là 0.13981866382421893
. Biến hasHandledARequest
cũng thay đổi thành true
. Giờ đây, trình chạy dịch vụ sẽ ở trạng thái rảnh một chút, vì vậy, trình duyệt sẽ dừng trình chạy dịch vụ. Lần tới khi có yêu cầu, trình duyệt sẽ cần đến worker dịch vụ một lần nữa nên sẽ đánh thức worker dịch vụ. Tập lệnh của nó được đánh giá lại. Bây giờ, hasHandledARequest
được đặt lại thành false
và favoriteNumber
là một giá trị hoàn toàn khác – 0.5907281835659033
.
Bạn không thể dựa vào trạng thái đã lưu trữ trong trình chạy dịch vụ. Ngoài ra, việc tạo các thực thể của những thứ như Kênh thông báo có thể gây ra lỗi: bạn sẽ nhận được một thực thể hoàn toàn mới mỗi khi worker dịch vụ dừng/bắt đầu.
Trong Service Workies chương 3, chúng ta hình dung trình chạy dịch vụ đã dừng sẽ mất hết màu sắc trong khi chờ được đánh thức.
Cùng nhau nhưng riêng biệt
Mỗi lần, trang của bạn chỉ có thể được một trình chạy dịch vụ kiểm soát. Tuy nhiên, ứng dụng có thể cài đặt hai worker cùng lúc. Khi thực hiện thay đổi đối với mã trình chạy dịch vụ và làm mới trang, bạn thực sự không chỉnh sửa trình chạy dịch vụ. Trình chạy dịch vụ không thể thay đổi. Thay vào đó, bạn sẽ tạo một trang doanh nghiệp hoàn toàn mới. Worker dịch vụ mới này (gọi là SW2) sẽ cài đặt nhưng chưa kích hoạt. Ứng dụng này phải wait trình chạy dịch vụ hiện tại (SW1) chấm dứt (khi người dùng rời khỏi trang web của bạn).
Can thiệp vào bộ nhớ đệm của một trình chạy dịch vụ khác
Trong khi cài đặt, SW2 có thể thiết lập mọi thứ – thường là tạo và điền bộ nhớ đệm. Tuy nhiên, hãy lưu ý: worker dịch vụ mới này có quyền truy cập vào mọi thứ mà worker dịch vụ hiện tại có quyền truy cập. Nếu bạn không cẩn thận, trình chạy dịch vụ đang chờ mới có thể thực sự làm rối tung mọi thứ cho trình chạy dịch vụ hiện tại. Sau đây là một số ví dụ có thể gây rắc rối cho bạn:
- SW2 có thể xoá bộ nhớ đệm mà SW1 đang sử dụng.
- SW2 có thể chỉnh sửa nội dung của bộ nhớ đệm mà SW1 đang sử dụng, khiến SW1 phản hồi bằng các thành phần mà trang không mong đợi.
Bỏ qua skipWaiting
Trình chạy dịch vụ cũng có thể sử dụng phương thức skipWaiting()
có rủi ro để kiểm soát trang ngay khi cài đặt xong. Đây thường là một ý tưởng không hay, trừ phi bạn cố tình thay thế một worker dịch vụ bị lỗi. Worker dịch vụ mới có thể đang sử dụng các tài nguyên đã cập nhật mà trang hiện tại không mong đợi, dẫn đến lỗi và sự cố.
Bắt đầu dọn dẹp
Cách để ngăn các worker dịch vụ của bạn can thiệp lẫn nhau là đảm bảo rằng chúng sử dụng các bộ nhớ đệm khác nhau. Cách dễ nhất để thực hiện việc này là tạo phiên bản cho tên bộ nhớ đệm mà chúng sử dụng.
const version = 1;
const assetCacheName = `assets-${version}`;
self.addEventListener("install", event => {
caches.open(assetCacheName).then(cache => {
// confidently do stuff with your very own cache
});
});
Khi triển khai một trình chạy dịch vụ mới, bạn sẽ đẩy version
để trình chạy dịch vụ đó thực hiện những việc cần thiết bằng một bộ nhớ đệm hoàn toàn tách biệt với trình chạy dịch vụ trước đó.
Kết thúc việc dọn dẹp
Khi worker dịch vụ của bạn đạt đến trạng thái activated
, bạn sẽ biết rằng worker đó đã tiếp quản và worker dịch vụ trước đó không còn cần thiết nữa. Tại thời điểm này, điều quan trọng là bạn phải dọn dẹp sau khi trình chạy dịch vụ cũ. Không chỉ tuân thủ giới hạn bộ nhớ đệm của người dùng, mà còn có thể ngăn chặn các lỗi ngoài ý muốn.
Phương thức caches.match()
là một lối tắt thường dùng để truy xuất một mục từ bất kỳ bộ nhớ đệm nào có kết quả trùng khớp. Tuy nhiên, phương thức này lặp lại qua các bộ nhớ đệm theo thứ tự tạo. Giả sử bạn có hai phiên bản tệp tập lệnh app.js
trong hai bộ nhớ đệm khác nhau – assets-1
và assets-2
. Trang của bạn đang chờ tập lệnh mới hơn được lưu trữ trong assets-2
. Tuy nhiên, nếu bạn chưa xoá bộ nhớ đệm cũ, caches.match('app.js')
sẽ trả về bộ nhớ đệm cũ từ assets-1
và rất có thể sẽ làm hỏng trang web của bạn.
Tất cả những gì bạn cần làm để dọn dẹp sau các worker dịch vụ trước đó là xoá mọi bộ nhớ đệm mà worker dịch vụ mới không cần đến:
const version = 2;
const assetCacheName = `assets-${version}`;
self.addEventListener("activate", event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== assetCacheName){
return caches.delete(cacheName);
}
});
);
});
);
});
Bạn cần phải nỗ lực và kỷ luật để ngăn các trình chạy dịch vụ của mình xung đột với nhau, nhưng điều này đáng để bạn làm.
Tư duy về trình chạy dịch vụ
Việc có tư duy đúng đắn khi suy nghĩ về worker dịch vụ sẽ giúp bạn tự tin xây dựng worker dịch vụ. Sau khi nắm được các tính năng này, bạn sẽ có thể tạo ra trải nghiệm tuyệt vời cho người dùng.
Nếu muốn hiểu tất cả những điều này bằng cách chơi trò chơi, thì bạn thật may mắn! Hãy chơi Service Workies để tìm hiểu cách thức hoạt động của worker dịch vụ nhằm tiêu diệt các quái vật ngoại tuyến.