Giới thiệu về Tìm nạp trong nền

Jake Archibald
Jake Archibald

Vào năm 2015, chúng tôi đã ra mắt tính năng Đồng bộ hoá trong nền. Tính năng này cho phép worker dịch vụ hoãn công việc cho đến khi người dùng có kết nối. Điều này có nghĩa là người dùng có thể nhập tin nhắn, nhấn nút gửi và rời khỏi trang web mà vẫn biết rằng tin nhắn sẽ được gửi ngay hoặc khi họ có kết nối.

Đây là một tính năng hữu ích, nhưng yêu cầu worker dịch vụ phải hoạt động trong suốt thời gian tìm nạp. Đó không phải là vấn đề đối với những công việc nhỏ như gửi tin nhắn, nhưng nếu tác vụ mất quá nhiều thời gian, trình duyệt sẽ huỷ service worker, nếu không, đó là một rủi ro đối với quyền riêng tư và pin của người dùng.

Vậy nếu bạn cần tải xuống nội dung có thể mất nhiều thời gian, chẳng hạn như phim, podcast hoặc các cấp độ của một trò chơi thì sao? Đó là mục đích của tính năng Tìm nạp ở chế độ nền.

Background Fetch (Tìm nạp trong nền) được cung cấp theo mặc định kể từ Chrome 74.

Sau đây là một bản minh hoạ nhanh dài 2 phút cho thấy trạng thái truyền thống của mọi thứ so với việc sử dụng tính năng Tìm nạp ở chế độ nền:

Cách hoạt động

Tính năng tìm nạp ở chế độ nền hoạt động như sau:

  1. Bạn yêu cầu trình duyệt thực hiện một nhóm tìm nạp ở chế độ nền.
  2. Trình duyệt tìm nạp những thứ đó và hiển thị tiến trình cho người dùng.
  3. Sau khi quá trình tìm nạp hoàn tất hoặc không thành công, trình duyệt sẽ mở trình chạy dịch vụ và kích hoạt một sự kiện để cho bạn biết điều gì đã xảy ra. Đây là nơi bạn quyết định cách xử lý các câu trả lời (nếu có).

Nếu người dùng đóng các trang trên trang web của bạn sau bước 1, thì không sao cả, quá trình tải xuống sẽ tiếp tục. Vì quá trình tìm nạp rất dễ thấy và dễ huỷ bỏ, nên không có vấn đề về quyền riêng tư đối với một tác vụ đồng bộ hoá trong nền quá dài. Vì service worker không chạy liên tục, nên không có lo ngại rằng service worker có thể lạm dụng hệ thống, chẳng hạn như khai thác bitcoin ở chế độ nền.

Trên một số nền tảng (chẳng hạn như Android), trình duyệt có thể đóng sau bước 1, vì trình duyệt có thể chuyển việc tìm nạp cho hệ điều hành.

Nếu người dùng bắt đầu tải xuống khi không có mạng hoặc chuyển sang chế độ không có mạng trong quá trình tải xuống, thì quá trình tìm nạp trong nền sẽ bị tạm dừng và tiếp tục sau đó.

API

Phát hiện tính năng

Giống như mọi tính năng mới, bạn cần phát hiện xem trình duyệt có hỗ trợ tính năng đó hay không. Đối với Background Fetch, bạn chỉ cần làm như sau:

if ('BackgroundFetchManager' in self) {
  // This browser supports Background Fetch!
}

Bắt đầu tìm nạp ở chế độ nền

API chính nằm ngoài quy trình đăng ký service worker, vì vậy, trước tiên, hãy đảm bảo bạn đã đăng ký một service worker. Sau đó:

navigator.serviceWorker.ready.then(async (swReg) => {
  const bgFetch = await swReg.backgroundFetch.fetch('my-fetch', ['/ep-5.mp3', 'ep-5-artwork.jpg'], {
    title: 'Episode 5: Interesting things.',
    icons: [{
      sizes: '300x300',
      src: '/ep-5-icon.png',
      type: 'image/png',
    }],
    downloadTotal: 60 * 1024 * 1024,
  });
});

backgroundFetch.fetch nhận 3 đối số:

Thông số
id string
xác định riêng biệt lệnh tìm nạp ở chế độ nền này.

backgroundFetch.fetch sẽ từ chối nếu mã nhận dạng khớp với một hoạt động tìm nạp nền hiện có.

requests Array<Request|string>
Những thứ cần tìm nạp. Các chuỗi sẽ được coi là URL và chuyển thành Request thông qua new Request(theString).

Bạn có thể tìm nạp nội dung từ các nguồn gốc khác miễn là tài nguyên cho phép thông qua CORS.

Lưu ý: Chrome hiện không hỗ trợ các yêu cầu cần có một yêu cầu kiểm tra trước CORS.

options Một đối tượng có thể bao gồm những nội dung sau:
options.title string
Tiêu đề mà trình duyệt sẽ hiển thị cùng với tiến trình.
options.icons Array<IconDefinition>
Một mảng các đối tượng có "src", "size" và "type".
options.downloadTotal number
Tổng kích thước của phần nội dung phản hồi (sau khi được giải nén).

Mặc dù không bắt buộc nhưng bạn nên cung cấp thông tin này. Tham số này được dùng để cho người dùng biết kích thước của nội dung tải xuống và cung cấp thông tin về tiến trình. Nếu bạn không cung cấp thông tin này, trình duyệt sẽ cho người dùng biết kích thước không xác định, do đó, người dùng có thể sẽ huỷ tải xuống.

Nếu số lượt tải xuống khi tìm nạp ở chế độ nền vượt quá số lượng được cung cấp ở đây, thì quá trình này sẽ bị huỷ. Sẽ không có vấn đề gì nếu kích thước tải xuống nhỏ hơn downloadTotal. Vì vậy, nếu không chắc chắn về tổng kích thước tải xuống, tốt nhất là bạn nên thận trọng.

backgroundFetch.fetch trả về một lời hứa được giải quyết bằng BackgroundFetchRegistration. Tôi sẽ trình bày chi tiết về vấn đề đó sau. Lệnh hứa từ chối nếu người dùng đã chọn không tải xuống hoặc một trong các tham số được cung cấp không hợp lệ.

Việc cung cấp nhiều yêu cầu cho một lần tìm nạp trong nền cho phép bạn kết hợp những thứ về mặt logic là một thứ duy nhất đối với người dùng. Ví dụ: một bộ phim có thể được chia thành hàng nghìn tài nguyên (thường là với MPEG-DASH) và đi kèm với các tài nguyên bổ sung như hình ảnh. Một cấp độ của trò chơi có thể trải rộng trên nhiều tài nguyên JavaScript, hình ảnh và âm thanh. Nhưng đối với người dùng, đó chỉ là "bộ phim" hoặc "cấp độ".

Nhận một hoạt động tìm nạp ở chế độ nền hiện có

Bạn có thể tìm nạp một nền hiện có như sau:

navigator.serviceWorker.ready.then(async (swReg) => {
  const bgFetch = await swReg.backgroundFetch.get('my-fetch');
});

…bằng cách truyền id của hoạt động tìm nạp trong nền mà bạn muốn. get sẽ trả về undefined nếu không có hoạt động tìm nạp dữ liệu ở chế độ nền nào đang hoạt động có mã nhận dạng đó.

Một hoạt động tìm nạp ở chế độ nền được coi là "đang hoạt động" từ thời điểm được đăng ký cho đến khi thành công, thất bại hoặc bị huỷ.

Bạn có thể nhận được danh sách tất cả các hoạt động tìm nạp ở chế độ nền đang hoạt động bằng cách sử dụng getIds:

navigator.serviceWorker.ready.then(async (swReg) => {
  const ids = await swReg.backgroundFetch.getIds();
});

Đăng ký tìm nạp ở chế độ nền

BackgroundFetchRegistration (bgFetch trong các ví dụ trên) có những thuộc tính sau:

Thuộc tính
id string
Mã nhận dạng của hoạt động tìm nạp trong nền.
uploadTotal number
Số byte cần gửi đến máy chủ.
uploaded number
Số byte đã gửi thành công.
downloadTotal number
Giá trị được cung cấp khi quá trình tìm nạp trong nền được đăng ký hoặc bằng 0.
downloaded number
Số byte đã nhận được.

Giá trị này có thể giảm. Ví dụ: nếu kết nối bị gián đoạn và không thể tiếp tục tải xuống, thì trình duyệt sẽ khởi động lại quá trình tìm nạp tài nguyên đó từ đầu.

result

Một trong số sau:

  • "" – Tính năng tìm nạp trong nền đang hoạt động nên chưa có kết quả.
  • "success" – Đã tìm nạp dữ liệu trong nền thành công.
  • "failure" – Không tìm nạp được dữ liệu trong nền. Giá trị này chỉ xuất hiện khi quá trình tìm nạp trong nền hoàn toàn không thành công, chẳng hạn như trình duyệt không thể thử lại/tiếp tục.
failureReason

Một trong số sau:

  • "" – Không tìm nạp được dữ liệu trong nền.
  • "aborted" – Người dùng đã huỷ quá trình tìm nạp ở chế độ nền hoặc abort() đã được gọi.
  • "bad-status" – Một trong các phản hồi có trạng thái không ổn, ví dụ: 404.
  • "fetch-error" – Một trong các lần tìm nạp không thành công vì một số lý do khác, ví dụ: CORS, MIX, phản hồi một phần không hợp lệ hoặc lỗi mạng chung đối với một lần tìm nạp không thể thử lại.
  • "quota-exceeded" – Đã đạt đến hạn mức bộ nhớ trong quá trình tìm nạp dữ liệu ở chế độ nền.
  • "download-total-exceeded" – Đã vượt quá giá trị `downloadTotal` được cung cấp.
recordsAvailable boolean
Có thể truy cập vào các yêu cầu/phản hồi cơ bản không?

Sau khi giá trị này là false, bạn không thể sử dụng matchmatchAll.

Phương thức
abort() Trả về Promise<boolean>
Huỷ tìm nạp trong nền.

Lời hứa được trả về sẽ phân giải với giá trị true nếu lệnh tìm nạp bị huỷ thành công.

matchAll(request, opts) Trả về Promise<Array<BackgroundFetchRecord>>
Nhận các yêu cầu và phản hồi.

Các đối số ở đây cũng giống như API bộ nhớ đệm. Gọi mà không có đối số sẽ trả về một lời hứa cho tất cả các bản ghi.

Hãy xem mục bên dưới để biết thêm thông tin.

match(request, opts) Trả về Promise<BackgroundFetchRecord>
Tương tự như trên, nhưng giải quyết bằng kết quả khớp đầu tiên.
Sự kiện
progress Kích hoạt khi có bất kỳ thay đổi nào về uploaded, downloaded, result hoặc failureReason.

Theo dõi tiến trình

Bạn có thể thực hiện việc này thông qua sự kiện progress. Hãy nhớ rằng downloadTotal là bất kỳ giá trị nào bạn đã cung cấp hoặc 0 nếu bạn không cung cấp giá trị.

bgFetch.addEventListener('progress', () => {
  // If we didn't provide a total, we can't provide a %.
  if (!bgFetch.downloadTotal) return;

  const percent = Math.round(bgFetch.downloaded / bgFetch.downloadTotal * 100);
  console.log(`Download progress: ${percent}%`);
});

Nhận yêu cầu và phản hồi

bgFetch.match('/ep-5.mp3').then(async (record) => {
  if (!record) {
    console.log('No record found');
    return;
  }

  console.log(`Here's the request`, record.request);
  const response = await record.responseReady;
  console.log(`And here's the response`, response);
});

record là một BackgroundFetchRecord và có dạng như sau:

Thuộc tính
request Request
Yêu cầu đã được cung cấp.
responseReady Promise<Response>
Phản hồi đã tìm nạp.

Phản hồi nằm sau một promise vì có thể chưa nhận được. Lệnh hứa sẽ từ chối nếu quá trình tìm nạp không thành công.

Sự kiện trình chạy dịch vụ

Sự kiện
backgroundfetchsuccess Đã tìm nạp thành công mọi thứ.
backgroundfetchfailure Một hoặc nhiều lần tìm nạp không thành công.
backgroundfetchabort Không tìm nạp được một hoặc nhiều lần.

Điều này chỉ thực sự hữu ích nếu bạn muốn dọn dẹp dữ liệu liên quan.

backgroundfetchclick Người dùng đã nhấp vào giao diện người dùng tiến trình tải xuống.

Các đối tượng sự kiện có những thuộc tính sau:

Thuộc tính
registration BackgroundFetchRegistration
Phương thức
updateUI({ title, icons }) Cho phép bạn thay đổi tiêu đề/biểu tượng mà bạn đã đặt ban đầu. Bạn không bắt buộc phải làm việc này, nhưng việc này giúp bạn cung cấp thêm bối cảnh nếu cần. Bạn chỉ có thể thực hiện việc này *một lần* trong các sự kiện backgroundfetchsuccessbackgroundfetchfailure.

Phản ứng với thành công/thất bại

Chúng ta đã thấy sự kiện progress, nhưng sự kiện này chỉ hữu ích khi người dùng mở một trang trên trang web của bạn. Lợi ích chính của tính năng tìm nạp trong nền là mọi thứ vẫn hoạt động sau khi người dùng rời khỏi trang hoặc thậm chí đóng trình duyệt.

Nếu quá trình tìm nạp trong nền hoàn tất thành công, trình chạy dịch vụ của bạn sẽ nhận được sự kiện backgroundfetchsuccessevent.registration sẽ là hoạt động đăng ký tìm nạp trong nền.

Sau sự kiện này, bạn sẽ không truy cập được vào các yêu cầu và phản hồi đã tìm nạp nữa. Vì vậy, nếu muốn giữ lại, hãy di chuyển chúng đến một nơi nào đó như API bộ nhớ đệm.

Giống như hầu hết các sự kiện của worker dịch vụ, hãy sử dụng event.waitUntil để worker dịch vụ biết thời điểm sự kiện hoàn tất.

Ví dụ: trong service worker của bạn:

addEventListener('backgroundfetchsuccess', (event) => {
  const bgFetch = event.registration;

  event.waitUntil(async function() {
    // Create/open a cache.
    const cache = await caches.open('downloads');
    // Get all the records.
    const records = await bgFetch.matchAll();
    // Copy each request/response across.
    const promises = records.map(async (record) => {
      const response = await record.responseReady;
      await cache.put(record.request, response);
    });

    // Wait for the copying to complete.
    await Promise.all(promises);

    // Update the progress notification.
    event.updateUI({ title: 'Episode 5 ready to listen!' });
  }());
});

Lỗi có thể chỉ là một lỗi 404 duy nhất, có thể không quan trọng đối với bạn, vì vậy, bạn vẫn nên sao chép một số phản hồi vào bộ nhớ đệm như trên.

Phản hồi khi nhấp

Người dùng có thể nhấp vào giao diện người dùng hiển thị tiến trình và kết quả tải xuống. Sự kiện backgroundfetchclick trong trình chạy dịch vụ cho phép bạn phản ứng với điều này. Như trên, event.registration sẽ là chế độ đăng ký tìm nạp trong nền.

Việc thường làm với sự kiện này là mở một cửa sổ:

addEventListener('backgroundfetchclick', (event) => {
  const bgFetch = event.registration;

  if (bgFetch.result === 'success') {
    clients.openWindow('/latest-podcasts');
  } else {
    clients.openWindow('/download-progress');
  }
});

Tài nguyên khác

Đính chính: Phiên bản trước của bài viết này đã đề cập không chính xác đến Background Fetch (Tìm nạp trong nền) là "tiêu chuẩn web". API hiện không nằm trong danh sách các tiêu chuẩn, bạn có thể tìm thấy quy cách trong WICG dưới dạng Báo cáo nháp của Nhóm cộng đồng.