Cuộc sống của nhân viên dịch vụ

Thật khó để biết được trình chạy dịch vụ đang làm gì nếu không hiểu vòng đời của chúng. Cơ chế hoạt động bên trong của chúng có vẻ mơ hồ, thậm chí là tuỳ ý. Bạn nên nhớ rằng, giống như mọi API trình duyệt khác, các hành vi của trình chạy dịch vụ đều được xác định rõ ràng, chỉ định rõ ràng và cho phép sử dụng các ứng dụng ngoại tuyến, đồng thời hỗ trợ việc cập nhật mà không làm gián đoạn trải nghiệm người dùng.

Trước khi tìm hiểu kỹ hơn về Workbox, bạn cần hiểu rõ vòng đời của trình chạy dịch vụ để làm rõ những gì Workbox làm được.

Định nghĩa thuật ngữ

Trước khi tham gia vào vòng đời của trình chạy dịch vụ, bạn nên xác định một số thuật ngữ về cách vòng đời đó hoạt động.

Kiểm soát và phạm vi

Ý tưởng về kiểm soát rất quan trọng để hiểu cách hoạt động của trình chạy dịch vụ. Trang được mô tả là đang được kiểm soát bởi một trình chạy dịch vụ là một trang cho phép một trình chạy dịch vụ thay mặt nó chặn các yêu cầu mạng. Trình chạy dịch vụ này có mặt có thể làm việc cho trang trong một phạm vi nhất định.

Phạm vi

Phạm vi của trình chạy dịch vụ được xác định theo vị trí của trình chạy đó trên máy chủ web. Nếu một trình chạy dịch vụ chạy trên một trang ở /subdir/index.html và ở /subdir/sw.js, thì phạm vi của trình chạy dịch vụ đó là /subdir/. Để xem khái niệm về phạm vi thực tế, hãy xem ví dụ sau:

  1. Chuyển đến https://service-worker-scope-viewer.glitch.me/subdir/index.html. Một thông báo sẽ xuất hiện cho biết không có trình chạy dịch vụ nào đang kiểm soát trang. Tuy nhiên, trang đó đăng ký một trình chạy dịch vụ từ https://service-worker-scope-viewer.glitch.me/subdir/sw.js.
  2. Tải lại trang. Vì trình chạy dịch vụ đã được đăng ký và đang hoạt động nên trình chạy này sẽ kiểm soát trang. Bạn sẽ thấy một biểu mẫu chứa phạm vi, trạng thái hiện tại và URL của trình chạy dịch vụ này. Lưu ý: việc phải tải lại trang không liên quan gì đến phạm vi, mà là vòng đời của trình chạy dịch vụ (sẽ được giải thích sau).
  3. Bây giờ, hãy chuyển đến https://service-worker-scope-viewer.glitch.me/index.html. Mặc dù một trình chạy dịch vụ đã được đăng ký trên nguồn gốc này, nhưng vẫn có thông báo cho biết hiện không có trình chạy dịch vụ nào. Đó là do trang này không nằm trong phạm vi của trình chạy dịch vụ đã đăng ký.

Phạm vi giới hạn những trang mà trình chạy dịch vụ kiểm soát. Trong ví dụ này, điều đó có nghĩa là trình chạy dịch vụ được tải từ /subdir/sw.js chỉ có thể kiểm soát các trang nằm trong /subdir/ hoặc cây con của nó.

Ở trên là cách hoạt động của phạm vi theo mặc định, nhưng bạn có thể ghi đè phạm vi tối đa được phép bằng cách đặt tiêu đề phản hồi Service-Worker-Allowed, cũng như chuyển tuỳ chọn scope đến phương thức register.

Trừ phi có lý do chính đáng để giới hạn phạm vi của trình chạy dịch vụ trong một tập hợp con của một nguồn gốc, hãy tải một trình chạy dịch vụ từ thư mục gốc của máy chủ web để phạm vi của trình chạy dịch vụ càng rộng càng tốt và đừng lo lắng về tiêu đề Service-Worker-Allowed. Tính năng này đơn giản hơn rất nhiều đối với mọi người theo cách đó.

Khách hàng

Khi nói rằng một trình chạy dịch vụ đang kiểm soát một trang thì thực sự là đang kiểm soát một ứng dụng khách. Ứng dụng là bất kỳ trang nào đang mở có URL nằm trong phạm vi của trình chạy dịch vụ đó. Cụ thể, đây là các bản sao của WindowClient.

Vòng đời của một trình chạy dịch vụ mới

Để một trình chạy dịch vụ kiểm soát một trang, trước tiên, trang đó phải được đưa vào hoạt động. Hãy bắt đầu với điều gì sẽ xảy ra khi một nhân viên dịch vụ hoàn toàn mới được triển khai cho một trang web không có nhân viên dịch vụ đang hoạt động.

Đăng ký

Đăng ký là bước đầu tiên trong vòng đời của trình chạy dịch vụ:

<!-- In index.html, for example: -->
<script>
  // Don't register the service worker
  // until the page has fully loaded
  window.addEventListener('load', () => {
    // Is service worker available?
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('/sw.js').then(() => {
        console.log('Service worker registered!');
      }).catch((error) => {
        console.warn('Error registering service worker:');
        console.warn(error);
      });
    }
  });
</script>

Mã này chạy trên luồng chính và thực hiện những việc sau:

  1. Vì lượt truy cập đầu tiên của người dùng vào một trang web xảy ra mà không có trình chạy dịch vụ đăng ký, hãy đợi cho đến khi trang tải đầy đủ trước khi đăng ký một trình chạy dịch vụ. Điều này giúp tránh tranh chấp băng thông nếu trình chạy dịch vụ lưu trước bất kỳ nội dung nào vào bộ nhớ đệm.
  2. Mặc dù trình chạy dịch vụ được hỗ trợ tốt, nhưng quy trình kiểm tra nhanh sẽ giúp tránh lỗi trong các trình duyệt không được hỗ trợ.
  3. Khi trang được tải đầy đủ và nếu trình chạy dịch vụ được hỗ trợ, hãy đăng ký /sw.js.

Một số điều quan trọng cần hiểu là:

  • Trình chạy dịch vụ chỉ dùng được qua HTTPS hoặc localhost.
  • Nếu nội dung của một trình chạy dịch vụ chứa lỗi cú pháp, thì quá trình đăng ký sẽ không thành công và trình chạy dịch vụ đó sẽ bị loại bỏ.
  • Lời nhắc: trình chạy dịch vụ hoạt động trong một phạm vi nhất định. Ở đây, phạm vi là toàn bộ nguồn gốc, vì phạm vi này được tải từ thư mục gốc.
  • Khi quá trình đăng ký bắt đầu, trạng thái của trình chạy dịch vụ được đặt thành 'installing'.

Sau khi đăng ký xong, quá trình cài đặt sẽ bắt đầu.

Cài đặt

Trình chạy dịch vụ kích hoạt sự kiện install sau khi đăng ký. install chỉ được gọi một lần cho mỗi trình chạy dịch vụ và sẽ không kích hoạt lại cho đến khi được cập nhật. Bạn có thể đăng ký lệnh gọi lại cho sự kiện install trong phạm vi của worker bằng addEventListener:

// /sw.js
self.addEventListener('install', (event) => {
  const cacheKey = 'MyFancyCacheName_v1';

  event.waitUntil(caches.open(cacheKey).then((cache) => {
    // Add all the assets in the array to the 'MyFancyCacheName_v1'
    // `Cache` instance for later use.
    return cache.addAll([
      '/css/global.bc7b80b7.css',
      '/css/home.fe5d0b23.css',
      '/js/home.d3cc4ba4.js',
      '/js/jquery.43ca4933.js'
    ]);
  }));
});

Thao tác này sẽ tạo một phiên bản Cache mới và lưu trước các tài sản vào bộ nhớ đệm. Chúng ta sẽ có rất nhiều cơ hội để thảo luận về việc lưu trước vào bộ nhớ đệm sau này, vì vậy, hãy tập trung vào vai trò của event.waitUntil. event.waitUntil chấp nhận lời hứa và chờ cho đến khi lời hứa đó được giải quyết. Trong ví dụ này, lời hứa đó thực hiện hai việc không đồng bộ:

  1. Tạo một thực thể Cache mới có tên là 'MyFancyCache_v1'.
  2. Sau khi bộ nhớ đệm được tạo, một mảng URL tài sản sẽ được lưu trước vào bộ nhớ đệm bằng phương thức addAll không đồng bộ.

Không cài đặt được nếu(các) lời hứa được chuyển đến event.waitUntil bị từ chối. Nếu điều này xảy ra, trình chạy dịch vụ sẽ bị loại bỏ.

Nếu resolve hứa hẹn, cài đặt thành công và trạng thái của trình chạy dịch vụ sẽ thay đổi thành 'installed', sau đó sẽ được kích hoạt.

Cách triển khai

Nếu đăng ký và cài đặt thành công, trình chạy dịch vụ sẽ kích hoạt và trạng thái của trình chạy này trở thành 'activating' Bạn có thể thực hiện công việc trong quá trình kích hoạt ở sự kiện activate của trình chạy dịch vụ này. Nhiệm vụ điển hình trong sự kiện này là cắt giảm bộ nhớ đệm cũ. Tuy nhiên, đối với một trình chạy dịch vụ hoàn toàn mới, điều này không phù hợp vào lúc này và sẽ được mở rộng khi chúng ta nói về thông tin cập nhật của trình chạy dịch vụ.

Đối với trình chạy dịch vụ mới, activate sẽ kích hoạt ngay sau khi install thành công. Sau khi kích hoạt xong, trạng thái của trình chạy dịch vụ sẽ trở thành 'activated'. Lưu ý rằng, theo mặc định, trình chạy dịch vụ mới sẽ không bắt đầu kiểm soát trang cho đến lần điều hướng hoặc làm mới trang tiếp theo.

Xử lý các bản cập nhật của trình chạy dịch vụ

Sau khi triển khai trình chạy dịch vụ đầu tiên, bạn có thể cần cập nhật vào lúc khác. Ví dụ: bạn có thể phải cập nhật nếu có thay đổi trong logic xử lý yêu cầu hoặc lưu trước vào bộ nhớ đệm.

Thời điểm cập nhật

Các trình duyệt sẽ kiểm tra bản cập nhật cho một trình chạy dịch vụ khi:

  • Người dùng chuyển đến một trang trong phạm vi của trình chạy dịch vụ.
  • navigator.serviceWorker.register() được gọi bằng một URL khác với trình chạy dịch vụ hiện đã cài đặt – nhưng đừng thay đổi URL của trình chạy dịch vụ!
  • navigator.serviceWorker.register() được gọi bằng cùng một URL với trình chạy dịch vụ đã cài đặt, nhưng có phạm vi khác. Xin nhắc lại, hãy tránh điều này bằng cách giữ phạm vi ở gốc của nguồn gốc (nếu có thể).
  • Khi các sự kiện như 'push' hoặc 'sync' đã được kích hoạt trong vòng 24 giờ qua, nhưng đừng lo lắng về những sự kiện này.

Cách cập nhật

Việc biết được thời điểm trình duyệt cập nhật một trình chạy dịch vụ là rất quan trọng, nhưng "cách thức" cũng quan trọng. Giả sử URL hoặc phạm vi của một trình chạy dịch vụ không thay đổi, thì trình chạy dịch vụ đang được cài đặt sẽ chỉ cập nhật lên phiên bản mới nếu nội dung của phiên bản đó thay đổi.

Trình duyệt phát hiện các thay đổi theo một vài cách:

  • Mọi thay đổi theo byte đối với tập lệnh do importScripts yêu cầu, nếu có.
  • Mọi thay đổi trong mã cấp cao nhất của trình chạy dịch vụ, ảnh hưởng đến vân tay số mà trình duyệt đã tạo ra.

Trình duyệt thực hiện rất nhiều công việc phức tạp ở đây. Để đảm bảo trình duyệt có mọi thứ cần thiết để phát hiện chính xác các thay đổi đối với nội dung của trình chạy dịch vụ, đừng yêu cầu bộ nhớ đệm HTTP giữ lại trình chạy này và đừng đổi tên tệp. Trình duyệt tự động thực hiện các kiểm tra cập nhật khi có điều hướng đến trang mới trong phạm vi của một trình chạy dịch vụ.

Kích hoạt các bước kiểm tra bản cập nhật theo cách thủ công

Liên quan đến các bản cập nhật, logic đăng ký thường không thay đổi. Tuy nhiên, một trường hợp ngoại lệ có thể là nếu các phiên trên một trang web tồn tại trong thời gian dài. Điều này có thể xảy ra trong các ứng dụng trang đơn mà trong đó rất hiếm khi có yêu cầu điều hướng, vì ứng dụng thường gặp một yêu cầu điều hướng khi bắt đầu vòng đời của ứng dụng. Trong những trường hợp như vậy, thao tác cập nhật thủ công có thể được kích hoạt trên luồng chính:

navigator.serviceWorker.ready.then((registration) => {
  registration.update();
});

Đối với các trang web truyền thống hoặc trong mọi trường hợp phiên người dùng không tồn tại lâu dài, việc kích hoạt cập nhật thủ công có thể không cần thiết.

Cài đặt

Khi sử dụng một trình gói để tạo các thành phần tĩnh, các thành phần đó sẽ chứa hàm băm trong tên, chẳng hạn như framework.3defa9d2.js. Giả sử một số thành phần trong số đó được lưu trước vào bộ nhớ đệm để truy cập ngoại tuyến sau này. Việc này sẽ yêu cầu cập nhật trình chạy dịch vụ để lưu trước các nội dung đã cập nhật vào bộ nhớ đệm:

self.addEventListener('install', (event) => {
  const cacheKey = 'MyFancyCacheName_v2';

  event.waitUntil(caches.open(cacheKey).then((cache) => {
    // Add all the assets in the array to the 'MyFancyCacheName_v2'
    // `Cache` instance for later use.
    return cache.addAll([
      '/css/global.ced4aef2.css',
      '/css/home.cbe409ad.css',
      '/js/home.109defa4.js',
      '/js/jquery.38caf32d.js'
    ]);
  }));
});

Có hai điều khác với ví dụ về sự kiện install đầu tiên so với trước đó:

  1. Tạo một thực thể Cache mới có khoá là 'MyFancyCacheName_v2'.
  2. Tên tài sản được lưu trước vào bộ nhớ đệm đã thay đổi.

Một điều cần lưu ý là một trình chạy dịch vụ đã cập nhật được cài đặt cùng với trình chạy trước đó. Điều này có nghĩa là trình chạy dịch vụ cũ vẫn có quyền kiểm soát mọi trang đang mở và sau khi cài đặt, trình chạy mới sẽ chuyển sang trạng thái chờ cho đến khi được kích hoạt.

Theo mặc định, một trình chạy dịch vụ mới sẽ kích hoạt khi không có ứng dụng nào do ứng dụng cũ kiểm soát. Điều này xảy ra khi bạn đóng tất cả các thẻ đang mở của trang web có liên quan.

Cách triển khai

Khi một trình chạy dịch vụ cập nhật được cài đặt và giai đoạn chờ kết thúc, trình chạy này sẽ kích hoạt và trình chạy dịch vụ cũ sẽ bị loại bỏ. Một nhiệm vụ phổ biến cần thực hiện trong sự kiện activate của một trình chạy dịch vụ đã cập nhật là cắt giảm các bộ nhớ đệm cũ. Xoá bộ nhớ đệm cũ bằng cách lấy khoá cho tất cả thực thể Cache đang mở bằng caches.keys và xoá những bộ nhớ đệm không nằm trong danh sách cho phép đã xác định bằng caches.delete:

self.addEventListener('activate', (event) => {
  // Specify allowed cache keys
  const cacheAllowList = ['MyFancyCacheName_v2'];

  // Get all the currently active `Cache` instances.
  event.waitUntil(caches.keys().then((keys) => {
    // Delete all caches that aren't in the allow list:
    return Promise.all(keys.map((key) => {
      if (!cacheAllowList.includes(key)) {
        return caches.delete(key);
      }
    }));
  }));
});

Các bộ nhớ đệm cũ không tự dọn dẹp. Chúng ta cần tự làm việc đó hoặc có nguy cơ vượt quá hạn mức bộ nhớ. Vì 'MyFancyCacheName_v1' trong trình chạy dịch vụ đầu tiên đã lỗi thời, nên danh sách cho phép bộ nhớ đệm sẽ được cập nhật để chỉ định 'MyFancyCacheName_v2'. Thao tác này sẽ xoá các bộ nhớ đệm có tên khác.

Sự kiện activate sẽ kết thúc sau khi xoá bộ nhớ đệm cũ. Tại thời điểm này, trình chạy dịch vụ mới sẽ nắm quyền kiểm soát trang, cuối cùng sẽ thay thế trình chạy cũ!

Vòng đời kéo dài

Dù dùng Workbox để xử lý việc triển khai và cập nhật trình chạy dịch vụ hay trực tiếp sử dụng API Trình chạy dịch vụ, bạn đều cần hiểu rõ vòng đời của trình chạy dịch vụ. Với sự hiểu biết đó, hành vi của nhân viên dịch vụ có vẻ logic hơn là bí ẩn.

Đối với những người muốn tìm hiểu sâu hơn về chủ đề này, bạn nên xem bài viết này của Jake Archibald. Có rất nhiều khác biệt về cách thức hoạt động của toàn bộ vòng đời dịch vụ diễn ra, nhưng điều đó dễ hiểu và kiến thức đó sẽ phát huy hiệu quả khi sử dụng Workbox.