Cách suy nghĩ về nhân viên 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. Nó có thể được cài đặt như một ứng dụng dành riêng cho nền tảng và mang lại cảm giác chỉn chu – nhưng với phạm vi tiếp cận và sự tự do của web.
Tuy nhiên, Service worker không giống bất cứ điều gì mà hầu hết các nhà phát triển web trong chúng ta đều quen thuộc. 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í giúp bạn hiểu rõ 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à nghĩ ra một số phép ẩn dụ giàu tính khắc hoạ. 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 nhau
Trong khi lập trình worker dịch vụ, bạn sẽ thấy nhiều thứ quen thuộc. Bạn sẽ có thể sử dụng các tính năng ngôn ngữ JavaScript mới mà mình yêu thích. Bạn theo dõi các sự kiện trong vòng đời tương tự như các sự kiện giao diện người dùng. Bạn quản lý quy trình kiểm soát bằng cách hứa hẹn như bạn đã quen thuộc.
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 chỉ đơn giản là chưa đủ để cập nhật trình chạy dịch vụ – cũng giống như việc bạn không mong đợi việc làm mới trang để cập nhật mã được triển khai trên máy chủ. Mỗi lớp có các quy tắc cập nhật riêng.
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 sử dụng trình chạy dịch vụ trên trang web sẽ mang lại cho bạn nhiều lợi ích đáng kể. 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ó được những cải tiến lớn về hiệu suất thông qua lưu vào bộ nhớ đệm
- sử dụng thông báo đẩy
- được cài đặt dưới dạng PWA
Dịch vụ có thể làm được nhiều nhất, nhưng bị hạn chế bởi 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.
Dài hạn nhưng ngắn hạ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 giữ trình chạy dịch vụ này để trình duyệt sẵn sàng vào 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, service worker có cơ hội để chặn nó 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 tôi trực quan hoá khái niệm này khi Kolohe (một nhân viên 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 worker dịch vụ có thể bị dừng 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 như 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
. Bây giờ, trình chạy dịch vụ sẽ không hoạt động trong một chút, vì vậy trình duyệt sẽ dừng nó. Lần tới khi có yêu cầu, trình chạy dịch vụ lại cần thiết, vì vậy trình duyệt sẽ đánh thức trình chạy 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 Quá trình làm việc dịch vụ chương 3, chúng ta hình dung trình chạy dịch vụ đã dừng của mình là bị mất toàn bộ màu sắc trong khi chờ đánh thức.
Cùng nhau nhưng riêng biệt
Chỉ một nhân viên dịch vụ có thể kiểm soát trang của bạn tại một thời điểm. 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 đang tạo một danh sách 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 chờ 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 muốn.
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. Có thể trình chạy dịch vụ mới đang sử dụng các tài nguyên đã cập nhật mà trang hiện tại không mong muốn, dẫn đến lỗi và lỗi.
Bắt đầu từ đầu
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, hàm 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 của một 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 mong đợi 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à tuân thủ một số quy tắc để ngăn 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 nhân viên phục 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.