Một khía cạnh quan trọng của Ứng dụng web tiến bộ là độ tin cậy; chúng có thể tải tài sản nhanh chóng, giữ chân người dùng và cung cấp thông tin phản hồi ngay lập tức, ngay cả trong điều kiện mạng kém. Sao có thể như vậy được? Nhờ sự kiện fetch
của worker dịch vụ.
Sự kiện tìm nạp
Sự kiện fetch
cho phép chúng ta chặn mọi yêu cầu mạng do PWA thực hiện trong phạm vi của worker dịch vụ, cho cả yêu cầu cùng nguồn gốc và yêu cầu khác nguồn gốc. Ngoài các yêu cầu về thành phần và điều hướng, việc tìm nạp từ một trình chạy dịch vụ đã cài đặt cho phép hiển thị các lượt truy cập trang sau lần tải đầu tiên của một trang web mà không cần gọi mạng.
Trình xử lý fetch
nhận tất cả các yêu cầu từ một ứng dụng, bao gồm cả URL và tiêu đề HTTP, đồng thời cho phép nhà phát triển ứng dụng quyết định cách xử lý các yêu cầu đó.
Trình chạy dịch vụ của bạn có thể chuyển tiếp yêu cầu đến mạng, phản hồi bằng một phản hồi đã được lưu vào bộ nhớ đệm trước đó hoặc tạo một phản hồi mới. Quyền quyết định là ở bạn! Sau đây là một ví dụ đơn giản:
self.addEventListener("fetch", event => { console.log(`URL requested: ${event.request.url}`); });
Trả lời yêu cầu
Khi một yêu cầu được gửi đến trình chạy dịch vụ, bạn có thể làm hai việc: bỏ qua yêu cầu đó để yêu cầu được gửi đến mạng hoặc phản hồi yêu cầu đó. Việc phản hồi các yêu cầu từ trong trình chạy dịch vụ là cách bạn có thể chọn nội dung và cách nội dung đó được trả về PWA của bạn, ngay cả khi người dùng không có mạng.
Để phản hồi một yêu cầu đến, hãy gọi event.respondWith()
từ bên trong trình xử lý sự kiện fetch
, như sau:
// fetch event handler in your service worker file self.addEventListener("fetch", event => { const response = .... // a response or a Promise of response event.respondWith(response); });
Bạn phải gọi respondWith()
một cách đồng bộ và phải trả về một đối tượng Response. Tuy nhiên, bạn không thể gọi respondWith()
sau khi trình xử lý sự kiện tìm nạp hoàn tất, chẳng hạn như trong một lệnh gọi không đồng bộ. Nếu cần đợi phản hồi hoàn chỉnh, bạn có thể truyền một Promise đến respondWith()
để phân giải bằng một Response.
Đang tạo câu trả lời
Nhờ Fetch API, bạn có thể tạo các phản hồi HTTP trong mã JavaScript của mình và các phản hồi đó có thể được lưu vào bộ nhớ đệm bằng Cache Storage API và được trả về như thể chúng đến từ một máy chủ web.
Để tạo một phản hồi, hãy tạo một đối tượng Response
mới, đặt nội dung và các lựa chọn của đối tượng đó, chẳng hạn như trạng thái và tiêu đề:
const simpleResponse = new Response("Body of the HTTP response"); const options = { status: 200, headers: { 'Content-type': 'text/html' } }; const htmlResponse = new Response("<b>HTML</b> content", options)
Phản hồi từ bộ nhớ đệm
Giờ đây, bạn đã biết cách phân phát các phản hồi HTTP từ một worker dịch vụ, đã đến lúc sử dụng giao diện Bộ nhớ đệm để lưu trữ các thành phần trên thiết bị.
Bạn có thể dùng API bộ nhớ đệm để kiểm tra xem yêu cầu nhận được từ PWA có trong bộ nhớ đệm hay không. Nếu có, hãy phản hồi respondWith()
bằng yêu cầu đó. Để làm như vậy, trước tiên, bạn cần tìm kiếm trong bộ nhớ đệm. Hàm match()
, có sẵn ở giao diện caches
cấp cao nhất, sẽ tìm kiếm tất cả các kho lưu trữ trong nguồn của bạn hoặc trên một đối tượng bộ nhớ đệm mở duy nhất.
Hàm match()
nhận một yêu cầu HTTP hoặc một URL làm đối số và trả về một promise phân giải bằng Phản hồi được liên kết với khoá tương ứng.
// Global search on all caches in the current origin caches.match(urlOrRequest).then(response => { console.log(response ? response : "It's not in the cache"); }); // Cache-specific search caches.open("pwa-assets").then(cache => { cache.match(urlOrRequest).then(response => { console.log(response ? response : "It's not in the cache"); }); });
Chiến lược lưu vào bộ nhớ đệm
Việc chỉ phân phát tệp từ bộ nhớ đệm của trình duyệt không phù hợp với mọi trường hợp sử dụng. Ví dụ: người dùng hoặc trình duyệt có thể xoá bộ nhớ đệm. Đó là lý do bạn nên xác định chiến lược riêng để phân phối các thành phần cho PWA của mình. Bạn không bị giới hạn ở một chiến lược lưu vào bộ nhớ đệm. Bạn có thể xác định các giá trị khác nhau cho các mẫu URL khác nhau. Ví dụ: bạn có thể có một chiến lược cho tài sản tối thiểu của giao diện người dùng, một chiến lược khác cho các lệnh gọi API và chiến lược thứ ba cho URL hình ảnh và dữ liệu. Để làm việc này, hãy đọc event.request.url
trong ServiceWorkerGlobalScope.onfetch
và phân tích cú pháp thông qua biểu thức chính quy hoặc Mẫu URL. (Tại thời điểm viết bài này, Mẫu URL không được hỗ trợ trên tất cả các nền tảng).
Sau đây là những chiến lược phổ biến nhất:
- Ưu tiên bộ nhớ đệm
- Trước tiên, hãy tìm kiếm một phản hồi được lưu vào bộ nhớ đệm và quay lại mạng nếu không tìm thấy phản hồi nào.
- Ưu tiên mạng
- Trước tiên, yêu cầu mạng phản hồi và nếu không có phản hồi nào được trả về, hãy kiểm tra phản hồi trong bộ nhớ đệm.
- Lỗi thời trong khi xác thực lại
- Phân phát phản hồi từ bộ nhớ đệm, đồng thời yêu cầu phiên bản mới nhất ở chế độ nền và lưu phiên bản đó vào bộ nhớ đệm cho lần tiếp theo yêu cầu tài sản.
- Chỉ có mạng
- Luôn trả lời bằng một phản hồi từ mạng hoặc báo lỗi. Bộ nhớ đệm không bao giờ được tham chiếu.
- Chỉ bộ nhớ đệm
- Luôn trả lời bằng một phản hồi từ bộ nhớ đệm hoặc báo lỗi. Mạng sẽ không bao giờ được tham khảo. Bạn phải thêm những thành phần sẽ được phân phát bằng chiến lược này vào bộ nhớ đệm trước khi yêu cầu.
Bộ nhớ đệm trước
Khi sử dụng chiến lược này, trình chạy dịch vụ sẽ tìm yêu cầu phù hợp trong bộ nhớ đệm và trả về Response tương ứng nếu yêu cầu đó được lưu vào bộ nhớ đệm. Nếu không, nó sẽ truy xuất phản hồi từ mạng (bạn có thể cập nhật bộ nhớ đệm cho các lệnh gọi trong tương lai). Nếu không có phản hồi từ bộ nhớ đệm và cũng không có phản hồi từ mạng, thì yêu cầu sẽ gặp lỗi. Vì việc phân phát các thành phần mà không cần truy cập vào mạng thường nhanh hơn, nên chiến lược này ưu tiên hiệu suất hơn độ mới.
self.addEventListener("fetch", event => { event.respondWith( caches.match(event.request) .then(cachedResponse => { // It can update the cache to serve updated content on the next request return cachedResponse || fetch(event.request); } ) ) });
Ưu tiên mạng
Chiến lược này là bản sao của chiến lược Cache First (Ưu tiên bộ nhớ đệm); chiến lược này kiểm tra xem yêu cầu có thể được thực hiện từ mạng hay không và nếu không thể, chiến lược này sẽ cố gắng truy xuất yêu cầu đó từ bộ nhớ đệm. Chẳng hạn như bộ nhớ đệm trước. Nếu không có phản hồi mạng cũng như phản hồi bộ nhớ đệm, thì yêu cầu sẽ gặp lỗi. Việc nhận phản hồi từ mạng thường chậm hơn so với việc nhận phản hồi từ bộ nhớ đệm. Chiến lược này ưu tiên nội dung mới cập nhật thay vì hiệu suất.
self.addEventListener("fetch", event => { event.respondWith( fetch(event.request) .catch(error => { return caches.match(event.request) ; }) ); });
Lỗi thời trong khi xác thực lại
Chiến lược cũ trong khi xác thực lại sẽ trả về ngay một phản hồi được lưu vào bộ nhớ đệm, sau đó kiểm tra mạng để tìm bản cập nhật, thay thế phản hồi được lưu vào bộ nhớ đệm nếu tìm thấy. Chiến lược này luôn đưa ra yêu cầu mạng, vì ngay cả khi tìm thấy một tài nguyên được lưu vào bộ nhớ đệm, chiến lược này sẽ cố gắng cập nhật nội dung trong bộ nhớ đệm bằng nội dung nhận được từ mạng, để sử dụng phiên bản đã cập nhật trong yêu cầu tiếp theo. Do đó, chiến lược này giúp bạn hưởng lợi từ việc phân phát nhanh của chiến lược ưu tiên bộ nhớ đệm và cập nhật bộ nhớ đệm ở chế độ nền.
self.addEventListener('fetch', event => { event.respondWith( caches.match(event.request).then(cachedResponse => { const networkFetch = fetch(event.request).then(response => { // update the cache with a clone of the network response const responseClone = response.clone() caches.open(url.searchParams.get('name')).then(cache => { cache.put(event.request, responseClone) }) return response }).catch(function (reason) { console.error('ServiceWorker fetch failed: ', reason) }) // prioritize cached response over network return cachedResponse || networkFetch } ) ) })
Chỉ với mạng
Chiến lược chỉ dùng mạng tương tự như cách các trình duyệt hoạt động mà không có trình chạy dịch vụ hoặc Cache Storage API. Các yêu cầu sẽ chỉ trả về một tài nguyên nếu có thể tìm nạp tài nguyên đó từ mạng. Điều này thường hữu ích cho các tài nguyên như yêu cầu API chỉ trực tuyến.
Chỉ bộ nhớ đệm
Chiến lược chỉ sử dụng bộ nhớ đệm đảm bảo rằng các yêu cầu không bao giờ được gửi đến mạng; tất cả các yêu cầu đến đều được phản hồi bằng một mục trong bộ nhớ đệm được điền sẵn. Đoạn mã sau đây sử dụng trình xử lý sự kiện fetch
với phương thức match
của bộ nhớ đệm để chỉ phản hồi bộ nhớ đệm:
self.addEventListener("fetch", event => { event.respondWith(caches.match(event.request)); });
Chiến lược tuỳ chỉnh
Mặc dù những điều trên là các chiến lược lưu vào bộ nhớ đệm phổ biến, nhưng bạn chịu trách nhiệm về service worker và cách xử lý các yêu cầu. Nếu không có lựa chọn nào phù hợp với nhu cầu của bạn, hãy tạo lựa chọn của riêng bạn.
Ví dụ: bạn có thể sử dụng chiến lược ưu tiên mạng với thời gian chờ để ưu tiên nội dung cập nhật, nhưng chỉ khi phản hồi xuất hiện trong ngưỡng mà bạn đặt. Bạn cũng có thể hợp nhất một phản hồi được lưu vào bộ nhớ đệm với một phản hồi mạng và tạo một phản hồi phức tạp từ trình chạy dịch vụ.
Đang cập nhật tài sản
Việc cập nhật các tài sản được lưu vào bộ nhớ đệm của PWA có thể là một thách thức. Mặc dù chiến lược cũ trong khi xác thực lại là một cách để thực hiện việc này, nhưng đó không phải là cách duy nhất. Trong chương Cập nhật, bạn sẽ tìm hiểu các kỹ thuật khác nhau để cập nhật nội dung và tài sản của ứng dụng.