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

Thật khó để biết trình chạy dịch vụ đang làm gì nếu không hiểu vòng đời của chúng. Hoạt động nội tại của họ có vẻ mơ hồ, thậm chí là tuỳ ý. Điều này cần nhớ rằng – giống như mọi API trình duyệt khác – hành vi của trình chạy dịch vụ đều được xác định rõ, đã chỉ định và cung cấp 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 đi sâu vào Workbox, bạn cần hiểu vòng đời của trình chạy dịch vụ để những gì Hộp công việc thực hiện có ý nghĩa.

Định nghĩa số hạng

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

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

Ý tưởng kiểm soát đóng vai trò quan trọng trong việc hiểu cách hoạt động của nhân viên dịch vụ. Trang được mô tả là do trình chạy dịch vụ kiểm soát là trang cho phép trình chạy dịch vụ thay mặt mình chặn các yêu cầu mạng. Service worker hiện diện có thể thực hiện tác vụ 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 dịch vụ đó trên máy chủ web. Nếu một trình chạy dịch vụ chạy trên một trang nằm tại /subdir/index.html và nằm tại /subdir/sw.js, phạm vi của trình chạy dịch vụ là /subdir/. Để xem khái niệm về phạm vi trong 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ó nhân viên 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à hiện đang hoạt động, đó là quyền kiểm soát trang. Một biểu mẫu chứa phạm vi của trình chạy dịch vụ, trạng thái hiện tại và URL của trạng thái đó sẽ được hiển thị. Lưu ý: việc phải tải lại trang không liên quan đến phạm vi, mà là vòng đời của trình chạy dịch vụ, nội dung này 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, vẫn có một thông báo cho biết hiện không có nhân viên dịch vụ nào. Đó là vì 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 sẽ 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 đây là cách tính năng xác định phạm vi hoạt động 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 một scope thành 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 nguồn gốc, tải 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. Theo cách đó, việc này đơn giản hơn rất nhiều đối với tất cả mọi người.

Khách hàng

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

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

Để trình chạy dịch vụ kiểm soát trang, nói cách khác là phải được hình thành trước tiên. Hãy bắt đầu với điều gì xảy ra khi một trình chạy dịch vụ hoàn toàn mới được triển khai cho một trang web không có trình chạy dịch vụ nào đ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. Bởi vì lượt truy cập đầu tiên của người dùng vào 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 được tải đầy đủ rồi mới đăng ký tài khoản. Đ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, việc kiểm tra nhanh giúp tránh lỗi trong các trình duyệt không hỗ trợ tính năng này.
  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 rõ là:

  • Trình chạy dịch vụ đang chỉ có sẵn qua HTTPS hoặc localhost.
  • Nếu nội dung của trình chạy dịch vụ chứa lỗi cú pháp, không đăng ký được và trình chạy dịch vụ bị huỷ.
  • Lời nhắc: trình chạy dịch vụ hoạt động trong một phạm vi. Ở đây, phạm vi là toàn bộ nguồn gốc, vì đượ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ý một 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 thực thể Cache mới và lưu trước các thành phần trong bộ nhớ đệm. Chúng ta sẽ có nhiều cơ hội để nói về việc thuyết trình vào lúc khác, nên 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 2 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 tạo bộ nhớ đệm, một mảng URL thành phần được lưu trước trong bộ nhớ đệm bằng cách sử dụng tính năng Phương thức addAll.

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 lời hứa giải quyết, 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ẽ 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' Có thể thực hiện công việc trong quá trình kích hoạt trong trình thực thi dịch vụ Sự kiện activate. Một tác vụ điển hình trong sự kiện này là cắt giảm các bộ nhớ đệm cũ, nhưng đối với một nhân viên hoàn toàn mới, hiện chưa phù hợp, và sẽ được mở rộng khi chúng ta nói về các bản 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 khi điều hướng hoặc làm mới trang tiếp theo.

Xử lý thông tin cập nhật về 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 sau. 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

Trình duyệt sẽ kiểm tra các bản cập nhật cho 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 cài đặt.nhưng khô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 với phạm vi khác. Một lần nữa, hãy tránh điều này bằng cách giữ phạm vi ở gốc của một 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 bạn đừng lo lắng về những sự kiện này.

Cách Google cập nhật

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

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

  • Mọi thay đổi từng byte đối với tập lệnh được yêu cầu bởi importScripts, 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 nhiều công việc nặng nhọc ở đây. Để đảm bảo trình duyệt có mọi thứ cần thiết để phát hiện các thay đổi đối với nội dung của trình chạy dịch vụ một cách đáng tin cậy, đừng yêu cầu bộ nhớ đệm HTTP lưu giữ tệp này cũng như cũng không được thay đổi tên tệp. Trình duyệt sẽ tự động thực hiện các lượt kiểm tra bản cập nhật khi có lệnh điều hướng đến một trang mới trong phạm vi của 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

Đối với bản cập nhật, logic đăng ký thường sẽ không thay đổi. Tuy nhiên, có một ngoại lệ có thể là nếu các phiên trên 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 khi yêu cầu chỉ đường hiếm khi xảy ra, vì ứng dụng thường gặp một yêu cầu điều hướng ở đầu vòng đời của ứng dụng. Trong những trường hợp như vậy, hệ thống có thể kích hoạt bản cập nhật thủ công 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 bất cứ trường hợp nào khi phiên của người dùng không tồn tại lâu dài, việc kích hoạt bản cập nhật thủ công có thể là không cần thiết.

Cài đặt

Khi sử dụng trình gói để tạo tài sản tĩnh, những nội dung đó sẽ chứa hàm băm trong tên, chẳng hạn như framework.3defa9d2.js. Giả sử một số nội dung đó được lưu trước vào bộ nhớ đệm để truy cập ngoại tuyến sau này. Việc này đòi hỏi trình chạy dịch vụ cập nhật các thành phần đã cập nhật trong 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'
    ]);
  }));
});

2 điều khác biệt với ví dụ về sự kiện install đầu tiên ở trước đó:

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

Một điều cần lưu ý là trình chạy dịch vụ đã cập nhật sẽ được cài đặt cùng với trình chạy dịch vụ 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 bất kỳ trang nào đang mở và sau khi cài đặt, mã 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 trình chạy dịch vụ cũ không có ứng dụng nào đang kiểm soát. Điều này xảy ra khi tất cả các thẻ đang mở cho trang web có liên quan bị đóng.

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, nó sẽ kích hoạt và trình chạy dịch vụ cũ sẽ bị loại bỏ. Một thao tác phổ biến cần thực hiện trong sự kiện activate của 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á các bộ nhớ đệm không có 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 phải tự làm, nếu không thì vượt quá hạn mức bộ nhớ. Vì 'MyFancyCacheName_v1' của trình chạy dịch vụ đầu tiên đã lỗi thời, 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á bộ nhớ đệm có tên khác.

Sự kiện activate sẽ kết thúc sau khi bộ nhớ đệm cũ bị xoá. 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 đã thay thế cái cũ!

Vòng đời không ngừng

Liệu Workbox có dùng để xử lý việc triển khai và cập nhật trình chạy dịch vụ hay không, hoặc sử dụng trực tiếp Service Worker API bạn sẽ trả tiền để hiểu vòng đời của trình chạy dịch vụ. Với hiểu biết đó, hành vi của nhân viên dịch vụ sẽ có vẻ hợp lý hơn là bí ẩn.

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