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, cho phép trình chạy dịch vụ trì 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 gửi và rời khỏi trang web khi biết rằng tin nhắn sẽ được gửi ngay bây giờ hoặc khi họ có kết nối.

Đây là một tính năng hữu ích nhưng đòi hỏi trình chạy 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 các công việc ngắn 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ẽ tắt trình chạy dịch vụ, nếu không thì có rủi ro đối với quyền riêng tư và pin của người dùng.

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

Tính năng Tìm nạp trong nền hoạt động theo mặc định kể từ Chrome 74.

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

Tự dùng thử bản minh hoạduyệt xem mã.

Cách thức hoạt động

Quy trình tìm nạp trong 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 tìm nạp xong hoặc không thành công, trình duyệt sẽ mở trình chạy dịch vụ của bạn 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 việc cần làm với câu trả lời (nếu có).

Nếu người dùng đóng các trang đến trang web của bạn sau bước 1 thì không có vấn đề gì, quá trình tải xuống sẽ tiếp tục. Vì hoạt động tìm nạp rất hiển thị và dễ dàng huỷ bỏ, nên bạn không cần lo ngại về quyền riêng tư khi thực hiện tác vụ đồng bộ hoá ở chế độ nền kéo dài. Vì trình chạy dịch vụ không hoạt động liên tục, nên bạn không cần lo lắng về việc trình chạy này có thể sử dụng hệ thống sai mục đích, chẳng hạn như đào 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 giao quá trình 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 không có kết nối 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ư bất kỳ tính năng mới nào, bạn nên xem trình duyệt có hỗ trợ tính năng đó hay không. Đối với Tìm nạp trong nền, quy trình này đơn giản như:

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

Bắt đầu tìm nạp trong nền

API chính sẽ tạm ngưng quy trình đăng ký service worker, vì vậy, hãy đảm bảo bạn đã đăng ký một trình chạy dịch vụ trước tiên. 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 ba đối số:

Tham số
id string
xác định duy nhất tệp tìm nạp trong nền này.

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

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

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

Lưu ý: Chrome hiện không hỗ trợ các yêu cầu cần phải kiểm tra CORS.

options Một đối tượng có thể bao gồm:
options.title string
Tiêu đề để trình duyệt 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 các nội dung phản hồi (sau khi giải nén).

Mặc dù đây là yêu cầu không bắt buộc nhưng bạn nên cung cấp thông tin này. Tệp này dùng để cho người dùng biết kích thước của tệp 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ẽ thông báo cho người dùng biết kích thước không xác định và do đó, người dùng có nhiều khả năng sẽ huỷ quá trình tải xuống.

Nếu tìm nạp trong nền vượt quá con số đã cung cấp ở đây, thì quá trình này sẽ bị hủy. Hoàn toàn không có vấn đề gì nếu tệp tải xuống nhỏ hơn downloadTotal. Vì vậy, nếu không biết chắc tổng số lượt tải xuống, tốt nhất bạn nên thận trọng.

backgroundFetch.fetch trả về một lời hứa mà sẽ phân giải bằng BackgroundFetchRegistration. Tôi sẽ trình bày chi tiết về chương trình này sau. Lời hứa sẽ bị từ chối nếu người dùng đã chọn không tải xuống hoặc một trong các thông 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 mọi thứ theo logic 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 thường 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ể được 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", hay "cấp độ".

Đang tải chế độ tìm nạp trong nền hiện có

Bạn có thể sử dụng phương thức tìm nạp trong 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 chuyển id (mã nhận dạng) của hoạt động tìm nạp trong nền mà bạn muốn. get trả về undefined nếu không có phương thức tìm nạp trong nền nào đang hoạt động với mã nhận dạng đó.

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

Bạn có thể lấy danh sách tất cả các lần tìm nạp trong 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ó:

Thuộc tính
id string
Mã nhận dạng của tìm nạp trong nền.
uploadTotal number
Số byte sẽ được 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 tìm nạp trong nền được đăng ký, hoặc giá trị đó là 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, trong trường hợp đó, trình duyệt sẽ khởi động lại quá trình tìm nạp cho tài nguyên đó từ đầu.

result

Một trong những giá trị 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 trong nền thành công.
  • "failure" – Tìm nạp trong nền không thành công. Giá trị này chỉ xuất hiện khi tìm nạp trong nền hoàn toàn không thành công, vì trình duyệt không thể thử lại/tiếp tục.
failureReason

Một trong những giá trị sau:

  • "" – Tìm nạp trong nền không thành công.
  • "aborted" – Người dùng huỷ trong nền khi tìm nạp hoặc abort() bị gọi.
  • "bad-status" - Một trong những phản hồi có trạng thái không ổn, ví dụ: 404.
  • "fetch-error" – Một trong những 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 cho một lần tìm nạp mà không thể thử lại.
  • "quota-exceeded" – Đã đạt đến hạn mức bộ nhớ trong quá trình tìm nạp ở chế độ nền.
  • "download-total-exceeded" – Đã vượt quá giá trị "downloadTotal" mà bạn 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?

Khi giá trị này là false, matchmatchAll sẽ không sử dụng được.

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 bằng giá trị true nếu huỷ tìm nạp thành công.

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

Các đối số ở đây giống như API bộ nhớ đệm. Việc gọi 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>
Như trên, nhưng phân giải bằng kết quả trùng khớp đầu tiên.
Sự kiện
progress Được kích hoạt khi bất kỳ uploaded, downloaded, result hoặc failureReason nào thay đổi.

Theo dõi tiến độ

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à giá trị bất kỳ mà 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à nó sẽ có dạng như sau:

Thuộc tính
request Request
Yêu cầu được đưa ra.
responseReady Promise<Response>
Câu trả lời đã tìm nạp.

Phản hồi này là lời hứa vì có thể chưa nhận được. Lời hứa sẽ bị từ chối nếu tìm nạp không thành công.

Sự kiện Service worker

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 Một hoặc nhiều lần tìm nạp không thành công.

Điều này chỉ thực sự hữu ích nếu bạn muốn xoá 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 đặc điểm 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 thực hiện việc này nhưng sẽ cung cấp thêm ngữ cảnh nếu cần. Bạn chỉ có thể làm việc này *một lần* trong các sự kiện backgroundfetchsuccessbackgroundfetchfailure.

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

Chúng ta đã thấy sự kiện progress, nhưng điều này chỉ hữu ích khi người dùng có một trang đang mở đế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 tiếp tục hoạt động sau khi người dùng rời khỏi trang hoặc thậm chí là đóng trình duyệt.

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

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

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

Ví dụ: trong trình chạy dịch vụ:

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ể đã xảy ra với một lỗi 404 mà 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.

Bày tỏ cảm xúc khi nhấp vào

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

Bạn có thể mở một cửa sổ để sử dụng sự kiện này:

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 gọi nhầm Tìm nạp trong nền là một "tiêu chuẩn web". API này hiện chưa có trong kênh tiêu chuẩn. Bạn có thể tìm thấy thông số kỹ thuật trong WICG dưới dạng Báo cáo nhóm cộng đồng nháp.