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

Jake Archibald
Jake Archibald

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 vào 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 yêu cầu worker phải hoạt động trong suốt quá trình tìm nạp. Đó không phải là vấn đề đối với các tác vụ 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 sẽ gây rủi ro cho quyền riêng tư và pin của người dùng.

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

Tính năng Tìm nạp ở chế độ nền được cung cấp 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 so với khi sử dụng tính năng Tìm nạp ở chế độ nền:

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

Cách hoạt động

Quá trình 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 lệnh tìm nạp ở chế độ nền.
  2. Trình duyệt sẽ tìm nạp những nội dung đó, 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ở worker 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 việc cần làm với các phản hồ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ì 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ỷ, nên không có vấn đề về quyền riêng tư khi một tác vụ đồng bộ hoá ở chế độ nền diễn ra quá lâu. Vì worker dịch vụ không chạy liên tục, nên bạn không cần lo lắng rằng worker dịch vụ 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 hoạt động tìm nạp sang 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ó mạng trong khi 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 muốn phát hiện xem trình duyệt có hỗ trợ tính năng đó hay không. Đối với tính năng Tìm nạp trong nền, bạn có thể thực hiện đơn giản như sau:

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

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

API chính sẽ treo quá trình đăng ký trình chạy dịch vụ, vì vậy, trước tiên hãy đảm bảo bạn đã đăng ký trình chạy dịch vụ. 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ượt 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 lệnh tìm nạp ở chế độ nền hiện có.

requests Array<Request|string>
Nội dung cần tìm nạp. 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 yêu cầu kiểm tra trước CORS.

options Một đối tượng có thể bao gồm những thành phần sau:
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 nội dung phản hồi (sau khi 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. Tệp này dùng để cho người dùng biết kích thước 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ẽ 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 tìm nạp ở chế độ nền vượt quá số lượng được cung cấp ở đây, thì tác vụ này sẽ bị huỷ. Bạn hoàn toàn có thể tải xuống tệp có kích thước nhỏ hơn downloadTotal. Vì vậy, nếu bạn không chắc chắn về tổng kích thước tệp tải xuống, tốt nhất bạn nên cẩn thận.

backgroundFetch.fetch trả về một lời hứa phân giải bằng BackgroundFetchRegistration. Tôi sẽ trình bày chi tiết về vấn đề đó sau. Lời hứa sẽ 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 ở chế độ 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 với người dùng. Ví dụ: một bộ phim có thể được chia thành 1.000 tài nguyên (thông thường với MPEG-DASH), đồng thời đ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 phân bổ 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à "phim" hoặc "cấp độ".

Tìm nạp một yêu cầu tìm nạp ở chế độ nền hiện có

Bạn có thể lấy một lệnh 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 truyền id của lệnh tìm nạp ở chế độ nền mà bạn muốn. get trả về undefined nếu không có hoạt động tìm nạp ở chế độ nền đang hoạt động bằng mã nhận dạng đó.

Một lượt 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, không thành công hoặc bị huỷ.

Bạn có thể nhận danh sách tất cả các lần 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();
});

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

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

Thuộc tính
id string
Mã nhận dạng của lần tìm nạp ở chế độ 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 bạn đăng ký tính năng tìm nạp ở chế độ nền hoặc giá trị bằng 0.
downloaded number
Số byte đã nhận được thành công.

Giá trị này có thể giảm. Ví dụ: nếu kết nối bị ngắt và không thể tiếp tục tải xuống, thì trình duyệt sẽ bắt đầu 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 ở chế độ nền đang hoạt động nên chưa có kết quả.
  • "success" – Tìm nạp ở chế độ nền thành công.
  • "failure" – Không tìm nạp được ở chế độ nền. Giá trị này chỉ xuất hiện khi quá trình tìm nạp ở chế độ 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 số sau:

  • "" – Quá trình tìm nạp ở chế độ nền không gặp lỗi.
  • "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ượt tìm nạp không thành công vì một số lý do khác, chẳng hạn như 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ượt tìm nạp không thể thử lại.
  • "quota-exceeded" – Đã đạt hạn mức bộ nhớ trong quá trình tìm nạp ở chế độ nền.
  • "download-total-exceeded" – Bạn đã vượt quá giới hạn `downloadTotal` đã 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à sai, bạn không thể sử dụng matchmatchAll.

Phương thức
abort() Trả về Promise<boolean>
Huỷ thao tác tìm nạp ở chế độ nền.

Lời hứa được trả về sẽ phân giải bằng true nếu quá trì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 giống với API bộ nhớ đệm. Việc gọi mà không có đối số sẽ trả về một lời hứa cho tất 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>
Giống như trên, nhưng phân giải bằng kết quả 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 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 được tìm nạp.

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

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

Sự kiện
backgroundfetchsuccess Đã tìm nạp xong 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 xoá các dữ liệu có 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ó:

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. Đây là phần không bắt buộc, nhưng cho phép bạn cung cấp thêm ngữ 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 ở chế độ 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í đóng trình duyệt.

Nếu quá trình tìm nạp ở chế độ nền hoàn tất thành công, worker dịch vụ sẽ nhận được sự kiện backgroundfetchsuccessevent.registration sẽ là lượt đăng ký tìm nạp ở chế độ 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 các yêu cầu và phản hồ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ụ như 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ể là do một lỗi 404 duy nhất, điều này 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 lượt nhấp

Bạn 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 thao tác này. Như trên, event.registration sẽ là đăng ký tìm nạp ở chế độ 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

Chỉnh sửa: Phiên bản trước của bài viết này đã gọi nhầm tính năng Tìm nạp ở chế độ nền là "tiêu chuẩn web". API này hiện không nằm trong lộ trì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.