Sổ tay nấu ăn ngoại tuyến

Jake Archibald
Jake Archibald

Với Trình chạy dịch vụ, chúng tôi đã ngừng cố gắng giải quyết chế độ ngoại tuyến và cung cấp cho nhà phát triển các phần chuyển động để tự giải quyết vấn đề. Trình quản lý này giúp bạn kiểm soát việc lưu vào bộ nhớ đệm và cách xử lý các yêu cầu. Điều đó có nghĩa là bạn có thể tạo các mẫu của riêng mình. Hãy cùng xem xét một vài mẫu có thể có khi tách riêng, nhưng trong thực tế, bạn có thể sẽ sử dụng nhiều mẫu cùng lúc tuỳ thuộc vào URL và ngữ cảnh.

Để xem bản minh hoạ hoạt động của một số mẫu này, hãy xem Trained-to-thrillvideo này cho thấy tác động đến hiệu suất.

Trình chạy dịch vụ cho phép bạn xử lý các yêu cầu độc lập với việc lưu vào bộ nhớ đệm, vì vậy, tôi sẽ minh hoạ riêng về các yêu cầu này. Trước tiên, việc lưu vào bộ nhớ đệm nên được thực hiện khi nào?

Khi cài đặt – dưới dạng phần phụ thuộc

Khi cài đặt – dưới dạng phần phụ thuộc.
Khi cài đặt – dưới dạng phần phụ thuộc.

Worker dịch vụ cung cấp cho bạn một sự kiện install. Bạn có thể sử dụng phương thức này để chuẩn bị mọi thứ, những thứ phải sẵn sàng trước khi bạn xử lý các sự kiện khác. Trong khi điều này xảy ra, mọi phiên bản trước đó của Worker dịch vụ vẫn đang chạy và phân phát trang, vì vậy, những việc bạn làm ở đây không được làm gián đoạn việc đó.

Loại tệp lý tưởng cho: CSS, hình ảnh, phông chữ, JS, mẫu… về cơ bản là mọi nội dung mà bạn coi là tĩnh đối với "phiên bản" trang web đó.

Đây là những nội dung sẽ khiến trang web của bạn không hoạt động hoàn toàn nếu không được tìm nạp, những nội dung mà một ứng dụng tương đương dành riêng cho nền tảng sẽ đưa vào quá trình tải xuống ban đầu.

self.addEventListener('install', function (event) {
  event
.waitUntil(
    caches
.open('mysite-static-v3').then(function (cache) {
     
return cache.addAll([
       
'/css/whatever-v3.css',
       
'/css/imgs/sprites-v6.png',
       
'/css/fonts/whatever-v8.woff',
       
'/js/all-min-v4.js',
       
// etc.
     
]);
   
}),
 
);
});

event.waitUntil thực hiện một lời hứa để xác định thời lượng và mức độ thành công của quá trình cài đặt. Nếu lời hứa từ chối, quá trình cài đặt sẽ được coi là không thành công và Trình chạy dịch vụ này sẽ bị bỏ qua (nếu phiên bản cũ hơn đang chạy, thì phiên bản đó sẽ được giữ nguyên). caches.open()cache.addAll() trả về các lời hứa. Nếu không tìm nạp được tài nguyên nào, lệnh gọi cache.addAll() sẽ từ chối.

Trên trained-to-thrill, tôi sử dụng tính năng này để lưu các thành phần tĩnh vào bộ nhớ đệm.

Khi cài đặt – không phải dưới dạng phần phụ thuộc

Khi cài đặt – không phải dưới dạng phần phụ thuộc.
Khi cài đặt – không phải dưới dạng phần phụ thuộc.

Cách này tương tự như trên, nhưng sẽ không trì hoãn quá trình cài đặt hoàn tất và sẽ không khiến quá trình cài đặt không thành công nếu không lưu vào bộ nhớ đệm.

Lý tưởng cho: các tài nguyên lớn hơn không cần thiết ngay lập tức, chẳng hạn như tài sản cho các cấp độ sau của trò chơi.

self.addEventListener('install', function (event) {
  event
.waitUntil(
    caches
.open('mygame-core-v1').then(function (cache) {
      cache
       
.addAll
       
// levels 11–20
       
();
     
return cache
       
.addAll
       
// core assets and levels 1–10
       
();
   
}),
 
);
});

Ví dụ trên không chuyển lời hứa cache.addAll cho các cấp 11–20 trở lại event.waitUntil, vì vậy, ngay cả khi không thành công, trò chơi vẫn có thể chơi ngoại tuyến. Tất nhiên, bạn sẽ phải tính đến khả năng không có các cấp đó và thử lưu lại các cấp đó vào bộ nhớ đệm nếu các cấp đó bị thiếu.

Trình chạy dịch vụ có thể bị tắt trong khi tải các cấp 11–20 xuống vì trình chạy dịch vụ đã xử lý xong các sự kiện, nghĩa là các sự kiện đó sẽ không được lưu vào bộ nhớ đệm. Trong tương lai, API Đồng bộ hoá định kỳ trong nền trên web sẽ xử lý các trường hợp như thế này và các tệp tải xuống lớn hơn như phim. API đó hiện chỉ được hỗ trợ trên các nhánh Chromium.

Khi kích hoạt

Khi kích hoạt.
Khi kích hoạt.

Lý tưởng cho: việc dọn dẹp và di chuyển.

Sau khi cài đặt một Worker dịch vụ mới và phiên bản trước đó không được sử dụng, phiên bản mới sẽ kích hoạt và bạn sẽ nhận được một sự kiện activate. Vì phiên bản cũ không còn được dùng nữa, nên đây là thời điểm thích hợp để xử lý việc di chuyển giản đồ trong IndexedDB và xoá bộ nhớ đệm không dùng đến.

self.addEventListener('activate', function (event) {
  event
.waitUntil(
    caches
.keys().then(function (cacheNames) {
     
return Promise.all(
        cacheNames
         
.filter(function (cacheName) {
           
// Return true if you want to remove this cache,
           
// but remember that caches are shared across
           
// the whole origin
         
})
         
.map(function (cacheName) {
           
return caches.delete(cacheName);
         
}),
     
);
   
}),
 
);
});

Trong quá trình kích hoạt, các sự kiện khác như fetch sẽ được đưa vào hàng đợi, vì vậy, việc kích hoạt lâu có thể chặn việc tải trang. Hãy giữ cho quá trình kích hoạt của bạn tinh gọn nhất có thể và chỉ sử dụng quá trình này cho những việc bạn không thể làm khi phiên bản cũ đang hoạt động.

Trên trained-to-thrill, tôi sử dụng phương thức này để xoá bộ nhớ đệm cũ.

Về hoạt động tương tác của người dùng

Khi người dùng tương tác.
Khi người dùng tương tác.

Phù hợp với: khi không thể đưa toàn bộ trang web vào chế độ ngoại tuyến và bạn chọn cho phép người dùng chọn nội dung họ muốn có sẵn khi không có mạng. Ví dụ: một video trên YouTube, một bài viết trên Wikipedia, một thư viện cụ thể trên Flickr.

Cung cấp cho người dùng nút "Đọc sau" hoặc "Lưu để dùng khi không có mạng". Khi được nhấp vào, hãy tìm nạp những gì bạn cần từ mạng và đưa vào bộ nhớ đệm.

document.querySelector('.cache-article').addEventListener('click', function (event) {
  event
.preventDefault();

 
var id = this.dataset.articleId;
  caches
.open('mysite-article-' + id).then(function (cache) {
    fetch
('/get-article-urls?id=' + id)
     
.then(function (response) {
       
// /get-article-urls returns a JSON-encoded array of
       
// resource URLs that a given article depends on
       
return response.json();
     
})
     
.then(function (urls) {
        cache
.addAll(urls);
     
});
 
});
});

API bộ nhớ đệm có sẵn trên các trang cũng như trình chạy dịch vụ, nghĩa là bạn có thể thêm vào bộ nhớ đệm ngay từ trang.

Về phản hồi mạng

Trên phản hồi mạng.
Khi phản hồi mạng.

Phù hợp với: các tài nguyên thường xuyên cập nhật, chẳng hạn như hộp thư đến của người dùng hoặc nội dung bài viết. Cũng rất hữu ích cho nội dung không cần thiết như hình đại diện, nhưng bạn cần phải cẩn thận.

Nếu một yêu cầu không khớp với bất kỳ nội dung nào trong bộ nhớ đệm, hãy lấy yêu cầu đó từ mạng, gửi yêu cầu đó đến trang và đồng thời thêm yêu cầu đó vào bộ nhớ đệm.

Nếu thực hiện việc này cho một loạt URL, chẳng hạn như hình đại diện, bạn cần cẩn thận để không làm tăng dung lượng lưu trữ của nguồn gốc. Nếu người dùng cần lấy lại dung lượng ổ đĩa, bạn không nên là ứng cử viên chính. Hãy nhớ xoá những mục không cần thiết trong bộ nhớ đệm.

self.addEventListener('fetch', function (event) {
  event
.respondWith(
    caches
.open('mysite-dynamic').then(function (cache) {
     
return cache.match(event.request).then(function (response) {
       
return (
          response
||
          fetch
(event.request).then(function (response) {
            cache
.put(event.request, response.clone());
           
return response;
         
})
       
);
     
});
   
}),
 
);
});

Để sử dụng bộ nhớ hiệu quả, bạn chỉ có thể đọc nội dung của một phản hồi/yêu cầu một lần. Mã ở trên sử dụng .clone() để tạo các bản sao bổ sung có thể được đọc riêng biệt.

Trên trained-to-thrill, tôi sử dụng tính năng này để lưu hình ảnh trên Flickr vào bộ nhớ đệm.

Stale-while-revalidate

Lỗi cũ trong khi xác thực lại.
Stale-while-revalidate.

Loại hình phù hợp: thường xuyên cập nhật tài nguyên mà không cần có phiên bản mới nhất. Hình đại diện có thể thuộc danh mục này.

Nếu có phiên bản được lưu vào bộ nhớ đệm, hãy sử dụng phiên bản đó, nhưng hãy tìm nạp bản cập nhật cho lần sau.

self.addEventListener('fetch', function (event) {
  event
.respondWith(
    caches
.open('mysite-dynamic').then(function (cache) {
     
return cache.match(event.request).then(function (response) {
       
var fetchPromise = fetch(event.request).then(function (networkResponse) {
          cache
.put(event.request, networkResponse.clone());
         
return networkResponse;
       
});
       
return response || fetchPromise;
     
});
   
}),
 
);
});

Điều này rất giống với stale-while-revalidate của HTTP.

Trên thông báo đẩy

Trên thông báo đẩy.
Trên thông báo đẩy.

Push API là một tính năng khác được xây dựng dựa trên Service Worker. Điều này cho phép đánh thức Worker dịch vụ để phản hồi một thông báo từ dịch vụ nhắn tin của hệ điều hành. Điều này xảy ra ngay cả khi người dùng không mở thẻ nào trên trang web của bạn. Chỉ có Service Worker được đánh thức. Bạn yêu cầu cấp quyền để thực hiện việc này từ một trang và người dùng sẽ được nhắc.

Phù hợp với: nội dung liên quan đến thông báo, chẳng hạn như tin nhắn trò chuyện, tin tức mới nhất hoặc email. Ngoài ra, nội dung thay đổi không thường xuyên sẽ được hưởng lợi từ tính năng đồng bộ hoá tức thì, chẳng hạn như nội dung cập nhật danh sách việc cần làm hoặc nội dung thay đổi lịch.

Kết quả cuối cùng thường thấy là một thông báo. Khi nhấn vào thông báo này, một trang có liên quan sẽ mở ra/được lấy làm tiêu điểm. Tuy nhiên, việc cập nhật bộ nhớ đệm trước khi điều này xảy ra là cực kỳ quan trọng. Rõ ràng là người dùng đang trực tuyến tại thời điểm nhận được thông báo đẩy, nhưng có thể họ không trực tuyến khi cuối cùng tương tác với thông báo. Vì vậy, việc cung cấp nội dung này khi không có mạng là rất quan trọng.

Mã này cập nhật bộ nhớ đệm trước khi hiển thị thông báo:

self.addEventListener('push', function (event) {
 
if (event.data.text() == 'new-email') {
    event
.waitUntil(
      caches
       
.open('mysite-dynamic')
       
.then(function (cache) {
         
return fetch('/inbox.json').then(function (response) {
            cache
.put('/inbox.json', response.clone());
           
return response.json();
         
});
       
})
       
.then(function (emails) {
          registration
.showNotification('New email', {
            body
: 'From ' + emails[0].from.name,
            tag
: 'new-email',
         
});
       
}),
   
);
 
}
});

self
.addEventListener('notificationclick', function (event) {
 
if (event.notification.tag == 'new-email') {
   
// Assume that all of the resources needed to render
   
// /inbox/ have previously been cached, e.g. as part
   
// of the install handler.
   
new WindowClient('/inbox/');
 
}
});

Trên background-sync

Trên chế độ đồng bộ hoá ở chế độ nền.
Trên chế độ đồng bộ hoá ở chế độ nền.

Đồng bộ hoá ở chế độ nền là một tính năng khác được xây dựng dựa trên Service Worker. API này cho phép bạn yêu cầu đồng bộ hoá dữ liệu ở chế độ nền một lần hoặc theo một khoảng thời gian (cực kỳ phỏng đoán). Điều này xảy ra ngay cả khi người dùng không mở thẻ nào trên trang web của bạn. Chỉ có Trình chạy dịch vụ được đánh thức. Bạn yêu cầu cấp quyền để thực hiện việc này từ một trang và người dùng sẽ được nhắc.

Phù hợp với: các bản cập nhật không khẩn cấp, đặc biệt là những bản cập nhật diễn ra thường xuyên đến mức thông báo đẩy cho mỗi bản cập nhật sẽ quá thường xuyên đối với người dùng, chẳng hạn như dòng thời gian trên mạng xã hội hoặc bài viết tin tức.

self.addEventListener('sync', function (event) {
 
if (event.id == 'update-leaderboard') {
    event
.waitUntil(
      caches
.open('mygame-dynamic').then(function (cache) {
       
return cache.add('/leaderboard.json');
     
}),
   
);
 
}
});

Khả năng lưu trữ cố định trong bộ nhớ đệm

Nguồn gốc của bạn được cấp một lượng dung lượng trống nhất định để làm những gì nó muốn. Dung lượng trống đó được chia sẻ giữa tất cả bộ nhớ gốc: Bộ nhớ(miền cục bộ), IndexedDB, Quyền truy cập vào hệ thống tệp và tất nhiên là Bộ nhớ đệm.

Số tiền bạn nhận được không được chỉ định. Thời gian sẽ khác nhau tuỳ thuộc vào thiết bị và điều kiện lưu trữ. Bạn có thể tìm hiểu số tiền mình nhận được thông qua:

if (navigator.storage && navigator.storage.estimate) {
 
const quota = await navigator.storage.estimate();
 
// quota.usage -> Number of bytes used.
 
// quota.quota -> Maximum number of bytes available.
 
const percentageUsed = (quota.usage / quota.quota) * 100;
  console
.log(`You've used ${percentageUsed}% of the available storage.`);
 
const remaining = quota.quota - quota.usage;
  console
.log(`You can write up to ${remaining} more bytes.`);
}

Tuy nhiên, giống như mọi bộ nhớ của trình duyệt, trình duyệt có thể xoá dữ liệu của bạn nếu thiết bị bị áp lực về bộ nhớ. Rất tiếc, trình duyệt không thể phân biệt giữa những bộ phim mà bạn muốn giữ lại bằng mọi giá và trò chơi mà bạn không thực sự quan tâm.

Để giải quyết vấn đề này, hãy sử dụng giao diện StorageManager:

// From a page:
navigator
.storage.persist()
.then(function(persisted) {
 
if (persisted) {
   
// Hurrah, your data is here to stay!
 
} else {
   
// So sad, your data may get chucked. Sorry.
});

Tất nhiên, người dùng phải cấp quyền. Để làm việc này, hãy sử dụng Permissions API.

Việc đưa người dùng vào quy trình này là rất quan trọng vì giờ đây, chúng ta có thể cho phép họ kiểm soát việc xoá. Nếu thiết bị của họ bị áp lực về bộ nhớ và việc xoá dữ liệu không cần thiết không giải quyết được vấn đề, thì người dùng sẽ đánh giá những mục cần giữ lại và xoá.

Để tính năng này hoạt động, hệ điều hành phải coi các nguồn gốc "bền vững" tương đương với các ứng dụng dành riêng cho nền tảng trong bảng chi tiết về mức sử dụng bộ nhớ, thay vì báo cáo trình duyệt dưới dạng một mục duy nhất.

Đề xuất phân phát – phản hồi yêu cầu

Không quan trọng bạn lưu bao nhiêu vào bộ nhớ đệm, worker dịch vụ sẽ không sử dụng bộ nhớ đệm trừ phi bạn cho biết thời điểm và cách thức. Dưới đây là một số mẫu để xử lý yêu cầu:

Chỉ bộ nhớ đệm

Chỉ bộ nhớ đệm.
Chỉ lưu vào bộ nhớ đệm.

Loại hình phù hợp: mọi nội dung mà bạn coi là tĩnh đối với một "phiên bản" cụ thể của trang web. Bạn nên lưu các tệp này vào bộ nhớ đệm trong sự kiện cài đặt để có thể dựa vào các tệp đó.

self.addEventListener('fetch', function (event) {
 
// If a match isn't found in the cache, the response
 
// will look like a connection error
  event
.respondWith(caches.match(event.request));
});

…mặc dù bạn thường không cần xử lý trường hợp này một cách cụ thể, nhưng phần Bộ nhớ đệm, quay lại mạng sẽ đề cập đến trường hợp này.

Chỉ với mạng

Chỉ mạng.
Chỉ có mạng.

Phù hợp với: những nội dung không có nội dung tương đương ngoại tuyến, chẳng hạn như ping phân tích, yêu cầu không phải GET.

self.addEventListener('fetch', function (event) {
  event
.respondWith(fetch(event.request));
 
// or simply don't call event.respondWith, which
 
// will result in default browser behavior
});

…mặc dù bạn thường không cần xử lý trường hợp này một cách cụ thể, nhưng phần Bộ nhớ đệm, quay lại mạng sẽ đề cập đến trường hợp này.

Bộ nhớ đệm, quay lại mạng

Bộ nhớ đệm, quay lại mạng.
Bộ nhớ đệm, quay lại mạng.

Phù hợp với: việc xây dựng ứng dụng có chế độ ngoại tuyến. Trong những trường hợp như vậy, bạn sẽ xử lý phần lớn các yêu cầu theo cách sau. Các mẫu khác sẽ là ngoại lệ dựa trên yêu cầu sắp tới.

self.addEventListener('fetch', function (event) {
  event
.respondWith(
    caches
.match(event.request).then(function (response) {
     
return response || fetch(event.request);
   
}),
 
);
});

Điều này cung cấp cho bạn hành vi "chỉ bộ nhớ đệm" cho những nội dung trong bộ nhớ đệm và hành vi "chỉ mạng" cho mọi nội dung không được lưu vào bộ nhớ đệm (bao gồm tất cả các yêu cầu không phải GET, vì chúng không thể được lưu vào bộ nhớ đệm).

Bộ nhớ đệm và cuộc đua mạng

Bộ nhớ đệm và cuộc đua mạng.
Cuộc đua bộ nhớ đệm và mạng.

Lý tưởng cho: các thành phần nhỏ mà bạn đang theo đuổi hiệu suất trên các thiết bị có tốc độ truy cập vào ổ đĩa chậm.

Với một số tổ hợp ổ đĩa cứng cũ, trình quét vi-rút và kết nối Internet nhanh hơn, việc lấy tài nguyên từ mạng có thể nhanh hơn so với việc truy cập vào ổ đĩa. Tuy nhiên, việc truy cập vào mạng khi người dùng có nội dung trên thiết bị có thể gây lãng phí dữ liệu, vì vậy, hãy lưu ý điều đó.

// Promise.race is no good to us because it rejects if
// a promise rejects before fulfilling. Let's make a proper
// race function:
function promiseAny(promises) {
 
return new Promise((resolve, reject) => {
   
// make sure promises are all promises
    promises
= promises.map((p) => Promise.resolve(p));
   
// resolve this promise as soon as one resolves
    promises
.forEach((p) => p.then(resolve));
   
// reject if all promises reject
    promises
.reduce((a, b) => a.catch(() => b)).catch(() => reject(Error('All failed')));
 
});
}

self
.addEventListener('fetch', function (event) {
  event
.respondWith(promiseAny([caches.match(event.request), fetch(event.request)]));
});

Mạng quay lại bộ nhớ đệm

Mạng quay lại bộ nhớ đệm.
Mạng sẽ quay lại bộ nhớ đệm.

Loại hình phù hợp: bản sửa lỗi nhanh cho các tài nguyên thường xuyên cập nhật, nằm ngoài "phiên bản" của trang web. Ví dụ: bài viết, hình đại diện, dòng thời gian trên mạng xã hội và bảng xếp hạng trò chơi.

Điều này có nghĩa là bạn cung cấp nội dung mới nhất cho người dùng trực tuyến, nhưng người dùng ngoại tuyến sẽ nhận được phiên bản cũ hơn được lưu vào bộ nhớ đệm. Nếu yêu cầu mạng thành công, rất có thể bạn sẽ muốn cập nhật mục nhập bộ nhớ đệm.

Tuy nhiên, phương thức này có một số điểm yếu. Nếu người dùng có kết nối không ổn định hoặc chậm, họ sẽ phải đợi mạng bị lỗi trước khi nhận được nội dung hoàn toàn chấp nhận được trên thiết bị của mình. Quá trình này có thể mất rất nhiều thời gian và gây ra trải nghiệm khó chịu cho người dùng. Hãy xem mẫu tiếp theo, Lưu vào bộ nhớ đệm rồi đến mạng, để biết giải pháp tốt hơn.

self.addEventListener('fetch', function (event) {
  event
.respondWith(
    fetch
(event.request).catch(function () {
     
return caches.match(event.request);
   
}),
 
);
});

Bộ nhớ đệm rồi đến mạng

Bộ nhớ đệm rồi đến mạng.
Lưu vào bộ nhớ đệm rồi đến mạng.

Phù hợp với: nội dung được cập nhật thường xuyên. Ví dụ: bài viết, dòng thời gian trên mạng xã hội và bảng xếp hạng trò chơi.

Điều này yêu cầu trang phải thực hiện hai yêu cầu, một yêu cầu đến bộ nhớ đệm và một yêu cầu đến mạng. Ý tưởng là trước tiên, hãy hiển thị dữ liệu đã lưu vào bộ nhớ đệm, sau đó cập nhật trang khi/nếu dữ liệu mạng đến.

Đôi khi, bạn chỉ cần thay thế dữ liệu hiện tại khi dữ liệu mới đến (ví dụ: bảng xếp hạng trò chơi), nhưng điều này có thể gây gián đoạn đối với các nội dung lớn hơn. Về cơ bản, đừng "biến mất" nội dung mà người dùng có thể đang đọc hoặc tương tác.

Twitter thêm nội dung mới phía trên nội dung cũ và điều chỉnh vị trí cuộn để người dùng không bị gián đoạn. Điều này có thể xảy ra vì Twitter chủ yếu giữ nguyên thứ tự nội dung theo tuyến tính. Tôi đã sao chép mẫu này cho trained-to-thrill để hiển thị nội dung trên màn hình nhanh nhất có thể, đồng thời hiển thị nội dung mới nhất ngay khi nội dung đó đến.

Mã trong trang:

var networkDataReceived = false;

startSpinner
();

// fetch fresh data
var networkUpdate = fetch('/data.json')
 
.then(function (response) {
   
return response.json();
 
})
 
.then(function (data) {
    networkDataReceived
= true;
    updatePage
(data);
 
});

// fetch cached data
caches
 
.match('/data.json')
 
.then(function (response) {
   
if (!response) throw Error('No data');
   
return response.json();
 
})
 
.then(function (data) {
   
// don't overwrite newer network data
   
if (!networkDataReceived) {
      updatePage
(data);
   
}
 
})
 
.catch(function () {
   
// we didn't get cached data, the network is our last hope:
   
return networkUpdate;
 
})
 
.catch(showErrorMessage)
 
.then(stopSpinner);

Mã trong Trình chạy dịch vụ:

Bạn phải luôn truy cập vào mạng và cập nhật bộ nhớ đệm khi di chuyển.

self.addEventListener('fetch', function (event) {
  event
.respondWith(
    caches
.open('mysite-dynamic').then(function (cache) {
     
return fetch(event.request).then(function (response) {
        cache
.put(event.request, response.clone());
       
return response;
     
});
   
}),
 
);
});

Trong trained-to-thrill, tôi đã giải quyết vấn đề này bằng cách sử dụng XHR thay vì fetch, và lạm dụng tiêu đề Accept để cho Trình chạy dịch vụ biết lấy kết quả từ đâu (mã trang, mã Trình chạy dịch vụ).

Dự phòng chung

Phương án dự phòng chung.
Bản dự phòng chung.

Nếu không phân phát được nội dung nào đó từ bộ nhớ đệm và/hoặc mạng, bạn nên cung cấp một phương án dự phòng chung.

Loại hình ảnh phù hợp: hình ảnh phụ như hình đại diện, yêu cầu POST không thành công và trang "Không có sẵn khi ngoại tuyến".

self.addEventListener('fetch', function (event) {
  event
.respondWith(
   
// Try the cache
    caches
     
.match(event.request)
     
.then(function (response) {
       
// Fall back to network
       
return response || fetch(event.request);
     
})
     
.catch(function () {
       
// If both fail, show a generic fallback:
       
return caches.match('/offline.html');
       
// However, in reality you'd have many different
       
// fallbacks, depending on URL and headers.
       
// Eg, a fallback silhouette image for avatars.
     
}),
 
);
});

Mục mà bạn sử dụng dự phòng có thể là một phần phụ thuộc cài đặt.

Nếu trang của bạn đang đăng email, thì worker dịch vụ có thể quay lại lưu trữ email trong "hộp thư đi" IndexedDB và phản hồi bằng cách cho trang biết rằng quá trình gửi không thành công nhưng dữ liệu đã được giữ lại thành công.

Tạo mẫu phía trình chạy dịch vụ

Tạo mẫu phía ServiceWorker.
Tạo mẫu phía ServiceWorker.

Phù hợp với: các trang không thể lưu phản hồi của máy chủ vào bộ nhớ đệm.

Việc hiển thị trang trên máy chủ giúp mọi thứ diễn ra nhanh chóng, nhưng điều đó có thể có nghĩa là bao gồm cả dữ liệu trạng thái có thể không có ý nghĩa trong bộ nhớ đệm, ví dụ: "Đã đăng nhập dưới dạng…". Nếu trang của bạn do một trình chạy dịch vụ kiểm soát, bạn có thể chọn yêu cầu dữ liệu JSON cùng với một mẫu và hiển thị dữ liệu đó.

importScripts('templating-engine.js');

self
.addEventListener('fetch', function (event) {
 
var requestURL = new URL(event.request.url);

  event
.respondWith(
   
Promise.all([
      caches
.match('/article-template.html').then(function (response) {
       
return response.text();
     
}),
      caches
.match(requestURL.path + '.json').then(function (response) {
       
return response.json();
     
}),
   
]).then(function (responses) {
     
var template = responses[0];
     
var data = responses[1];

     
return new Response(renderTemplate(template, data), {
        headers
: {
         
'Content-Type': 'text/html',
       
},
     
});
   
}),
 
);
});

Tổng hợp kiến thức đã học

Bạn không bị giới hạn ở một trong những phương thức này. Trên thực tế, bạn có thể sẽ sử dụng nhiều trong số đó tuỳ thuộc vào URL yêu cầu. Ví dụ: cụm từ trained-to-thrill (được huấn luyện để tạo cảm giác mạnh) sử dụng:

Bạn chỉ cần xem yêu cầu rồi quyết định việc cần làm:

self.addEventListener('fetch', function (event) {
 
// Parse the URL:
 
var requestURL = new URL(event.request.url);

 
// Handle requests to a particular host specifically
 
if (requestURL.hostname == 'api.example.com') {
    event
.respondWith(/* some combination of patterns */);
   
return;
 
}
 
// Routing for local URLs
 
if (requestURL.origin == location.origin) {
   
// Handle article URLs
   
if (/^\/article\//.test(requestURL.pathname)) {
      event
.respondWith(/* some other combination of patterns */);
     
return;
   
}
   
if (/\.webp$/.test(requestURL.pathname)) {
      event
.respondWith(/* some other combination of patterns */);
     
return;
   
}
   
if (request.method == 'POST') {
      event
.respondWith(/* some other combination of patterns */);
     
return;
   
}
   
if (/cheese/.test(requestURL.pathname)) {
      event
.respondWith(
       
new Response('Flagrant cheese error', {
          status
: 512,
       
}),
     
);
     
return;
   
}
 
}

 
// A sensible default pattern
  event
.respondWith(
    caches
.match(event.request).then(function (response) {
     
return response || fetch(event.request);
   
}),
 
);
});

…bạn hiểu ý tôi rồi.

Ghi công

…cho các biểu tượng đáng yêu:

Cảm ơn Jeff Posnick đã phát hiện nhiều lỗi nghiêm trọng trước khi tôi nhấn vào nút "xuất bản".

Tài liệu đọc thêm