ขอแนะนำการดึงข้อมูลในเบื้องหลัง

ในปี 2015 เราได้เปิดตัวการซิงค์ในเบื้องหลัง ซึ่งช่วยให้ Service Worker เลื่อนงานไปจนกว่าผู้ใช้จะมีการเชื่อมต่อ ซึ่งหมายความว่าผู้ใช้สามารถพิมพ์ข้อความ กดส่ง แล้วออกจากเว็บไซต์ได้ โดยที่ระบบจะส่งข้อความให้ทันทีหรือเมื่อผู้ใช้มีการเชื่อมต่อ

ฟีเจอร์นี้มีประโยชน์ แต่ Service Worker ต้องทำงานอยู่ตลอดระยะเวลาการดึงข้อมูล ซึ่งไม่มีปัญหาสำหรับงานสั้นๆ เช่น การส่งข้อความ แต่หากงานใช้เวลานานเกินไป เบราว์เซอร์จะหยุด Service Worker ดังกล่าว มิเช่นนั้นจะเป็นอันตรายต่อความเป็นส่วนตัวและแบตเตอรี่ของผู้ใช้

ในกรณีที่คุณต้องดาวน์โหลดเนื้อหาที่อาจใช้เวลานาน เช่น ภาพยนตร์ พอดแคสต์ หรือด่านของเกม การดึงข้อมูลในเบื้องหลังมีไว้เพื่อแก้ปัญหานี้

การดึงข้อมูลในเบื้องหลังพร้อมใช้งานโดยค่าเริ่มต้นตั้งแต่ Chrome 74

ลองดูการสาธิตสั้นๆ 2 นาทีนี้ที่แสดงสถานะแบบดั้งเดิมเทียบกับการใช้การดึงข้อมูลเบื้องหลัง

ลองใช้เดโมด้วยตนเองและเรียกดูโค้ด

วิธีการทำงาน

การดึงข้อมูลในเบื้องหลังจะทํางานดังนี้

  1. คุณบอกให้เบราว์เซอร์ดึงข้อมูลกลุ่มหนึ่งในเบื้องหลัง
  2. จากนั้นเบราว์เซอร์จะดึงข้อมูลเหล่านั้นมาแสดงความคืบหน้าต่อผู้ใช้
  3. เมื่อดึงข้อมูลเสร็จสมบูรณ์หรือไม่สําเร็จ เบราว์เซอร์จะเปิด Service Worker และเรียกเหตุการณ์เพื่อแจ้งให้คุณทราบสิ่งที่เกิดขึ้น ในส่วนนี้ คุณตัดสินใจได้ว่าจะทำอย่างไรกับคำตอบ (หากมี)

หากผู้ใช้ปิดหน้าเว็บในเว็บไซต์ของคุณหลังจากขั้นตอนที่ 1 ก็ไม่เป็นไร การดาวน์โหลดจะยังคงดำเนินต่อไป เนื่องจากการดึงข้อมูลนั้นสังเกตเห็นได้ชัดเจนและหยุดได้ง่าย จึงไม่ต้องกังวลเรื่องความเป็นส่วนตัวของงานซิงค์ในเบื้องหลังที่ใช้เวลานานเกินไป เนื่องจาก Service Worker ไม่ได้ทำงานอย่างต่อเนื่อง จึงไม่ต้องกังวลว่าอาจมีการละเมิดระบบ เช่น การขุด Bitcoin ในเบื้องหลัง

ในบางแพลตฟอร์ม (เช่น Android) เบราว์เซอร์อาจปิดหลังจากขั้นตอนที่ 1 เนื่องจากเบราว์เซอร์สามารถส่งต่อการดึงข้อมูลไปยังระบบปฏิบัติการได้

หากผู้ใช้เริ่มการดาวน์โหลดขณะออฟไลน์ หรือออฟไลน์ระหว่างการดาวน์โหลด ระบบจะหยุดการดึงข้อมูลในเบื้องหลังชั่วคราวและดึงข้อมูลต่อในภายหลัง

API

การตรวจหาองค์ประกอบ

เช่นเดียวกับฟีเจอร์ใหม่อื่นๆ คุณต้องตรวจสอบว่าเบราว์เซอร์รองรับฟีเจอร์นั้นหรือไม่ สําหรับการดึงข้อมูลในเบื้องหลัง เพียงทำดังนี้

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

การเริ่มดึงข้อมูลในเบื้องหลัง

API หลักจะทำงานร่วมกับการลงทะเบียน Service Worker ดังนั้นโปรดตรวจสอบว่าคุณได้ลงทะเบียน Service Worker ก่อน จากนั้นให้ทำดังนี้

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 ใช้อาร์กิวเมนต์ 3 รายการ ได้แก่

พารามิเตอร์
id string
จะระบุการดึงข้อมูลในเบื้องหลังนี้อย่างไม่ซ้ำกัน

backgroundFetch.fetch จะปฏิเสธหากรหัสตรงกับการดึงข้อมูลเบื้องหลังที่มีอยู่

requests Array<Request|string>
สิ่งที่จะดึงข้อมูล ระบบจะถือว่าสตริงเป็น URL และเปลี่ยนเป็น Request ผ่าน new Request(theString)

คุณสามารถดึงข้อมูลจากต้นทางอื่นๆ ได้ ตราบใดที่ทรัพยากรอนุญาตผ่าน CORS

หมายเหตุ: ปัจจุบัน Chrome ไม่รองรับคำขอที่ต้องใช้ CORS Preflight

options ออบเจ็กต์ที่อาจประกอบด้วยข้อมูลต่อไปนี้
options.title string
ชื่อสำหรับเบราว์เซอร์ที่จะแสดงพร้อมกับความคืบหน้า
options.icons Array<IconDefinition>
อาร์เรย์ของออบเจ็กต์ที่มี "src", "size" และ "type"
options.downloadTotal number
ขนาดทั้งหมดของเนื้อหาการตอบกลับ (หลังจากยกเลิกการบีบอัดไฟล์ ZIP)

แม้ว่าจะไม่ใช่ข้อมูลที่ต้องระบุ แต่เราขอแนะนําอย่างยิ่งให้คุณระบุข้อมูลนี้ ข้อมูลนี้ใช้เพื่อบอกผู้ใช้ว่าไฟล์ที่ดาวน์โหลดมีขนาดใหญ่เท่าใด และเพื่อแสดงข้อมูลความคืบหน้า หากคุณไม่ได้ระบุค่านี้ เบราว์เซอร์จะแจ้งให้ผู้ใช้ทราบว่าไม่ทราบขนาด ซึ่งอาจทำให้ผู้ใช้มีแนวโน้มที่จะยกเลิกการดาวน์โหลด

หากการดาวน์โหลดจากการดึงข้อมูลในเบื้องหลังมีจำนวนมากกว่าที่ระบุไว้ที่นี่ ระบบจะยกเลิกการดึงข้อมูล ไม่เป็นไรหากการดาวน์โหลดมีขนาดเล็กกว่า downloadTotal ดังนั้นหากคุณไม่แน่ใจว่าการดาวน์โหลดทั้งหมดจะเท่าใด ก็ควรเผื่อไว้ก่อน

backgroundFetch.fetch แสดงผลพรอมต์ที่แสดงผลเป็น BackgroundFetchRegistration เราจะอธิบายรายละเอียดในภายหลัง ระบบจะปฏิเสธการสัญญาหากผู้ใช้เลือกไม่ใช้การดาวน์โหลด หรือพารามิเตอร์ที่ระบุไม่ถูกต้อง

การส่งคําขอหลายรายการสําหรับการดึงข้อมูลเบื้องหลังครั้งเดียวช่วยให้คุณรวมข้อมูลต่างๆ ที่ผู้ใช้มองว่าเป็นสิ่งเดียวกันได้ ตัวอย่างเช่น ภาพยนตร์อาจแบ่งออกเป็นทรัพยากรหลายพันรายการ (โดยทั่วไปจะใช้กับ MPEG-DASH) และมาพร้อมกับทรัพยากรเพิ่มเติม เช่น รูปภาพ ด่านของเกมอาจกระจายอยู่ในทรัพยากร JavaScript, รูปภาพ และเสียงหลายรายการ แต่สำหรับผู้ใช้แล้ว รายการดังกล่าวเป็นเพียง "ภาพยนตร์" หรือ "ด่าน"

การดึงข้อมูลในเบื้องหลังที่มีอยู่

คุณดูการเรียกข้อมูลเบื้องหลังที่มีอยู่ได้ดังนี้

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

…โดยการส่งรหัสของการดึงข้อมูลเบื้องหลังที่ต้องการ get จะแสดงผลเป็น undefined หากไม่มีการดึงข้อมูลเบื้องหลังที่ใช้งานอยู่ซึ่งมีรหัสนั้น

การดึงข้อมูลในเบื้องหลังจะถือว่า "ทำงานอยู่" นับจากเวลาที่ลงทะเบียน จนกระทั่งดึงข้อมูลสำเร็จ ไม่สำเร็จ หรือถูกยกเลิก

คุณดูรายการการดึงข้อมูลเบื้องหลังที่ใช้งานอยู่ทั้งหมดได้โดยใช้ getIds ดังนี้

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

การลงทะเบียนการดึงข้อมูลในเบื้องหลัง

BackgroundFetchRegistration (bgFetch ในตัวอย่างด้านบน) มีข้อมูลต่อไปนี้

พร็อพเพอร์ตี้
id string
รหัสของการดึงข้อมูลในเบื้องหลัง
uploadTotal number
จํานวนไบต์ที่จะส่งไปยังเซิร์ฟเวอร์
uploaded number
จำนวนไบต์ที่ส่งสำเร็จ
downloadTotal number
ค่าที่ระบุเมื่อลงทะเบียนการดึงข้อมูลในเบื้องหลัง หรือ 0
downloaded number
จำนวนไบต์ที่ได้รับสําเร็จ

ค่านี้อาจลดลง ตัวอย่างเช่น หากการเชื่อมต่อขาดและดาวน์โหลดต่อไม่ได้ ในกรณีนี้เบราว์เซอร์จะเริ่มต้นการดึงข้อมูลทรัพยากรนั้นอีกครั้งตั้งแต่ต้น

result

ประเภทใดประเภทหนึ่งต่อไปนี้

  • "" - การดึงข้อมูลในเบื้องหลังทำงานอยู่ จึงยังไม่มีผลลัพธ์
  • "success" - การดึงข้อมูลในเบื้องหลังสําเร็จ
  • "failure" - การดึงข้อมูลในเบื้องหลังไม่สำเร็จ ค่านี้จะปรากฏก็ต่อเมื่อการดึงข้อมูลในเบื้องหลังล้มเหลวโดยสมบูรณ์ เนื่องจากเบราว์เซอร์ไม่สามารถลองใหม่/ดําเนินการต่อได้
failureReason

ประเภทใดประเภทหนึ่งต่อไปนี้

  • "" - การเรียกข้อมูลในเบื้องหลังไม่ล้มเหลว
  • "aborted" – ผู้ใช้ยกเลิกการดึงข้อมูลในเบื้องหลัง หรือมีการเรียกใช้ abort()
  • "bad-status" - การตอบกลับรายการใดรายการหนึ่งมีสถานะไม่ถูกต้อง เช่น 404
  • "fetch-error" - การดึงข้อมูลรายการใดรายการหนึ่งไม่สำเร็จเนื่องจากเหตุผลอื่นๆ เช่น CORS, MIX, การตอบกลับบางส่วนที่ไม่ถูกต้อง หรือเครือข่ายขัดข้องทั่วไปสำหรับการดึงข้อมูลซึ่งดึงซ้ำไม่ได้
  • "quota-exceeded" - โควต้าพื้นที่เก็บข้อมูลเต็มระหว่างการดึงข้อมูลในเบื้องหลัง
  • "download-total-exceeded" - มีจำนวนรายการที่ดาวน์โหลดเกิน `downloadTotal` ที่ระบุ
recordsAvailable boolean
เข้าถึงคำขอ/คำตอบที่เกี่ยวข้องได้ไหม

เมื่อเป็นเท็จ match และ matchAll จะใช้ไม่ได้

เมธอด
abort() แสดงผล Promise<boolean>
ยกเลิกการดึงข้อมูลในเบื้องหลัง

Promise ที่แสดงผลจะแสดงผลเป็น "จริง" หากยกเลิกการดึงข้อมูลได้สําเร็จ

matchAll(request, opts) แสดงผล Promise<Array<BackgroundFetchRecord>>
รับคําขอและคําตอบ

อาร์กิวเมนต์ที่นี่เหมือนกับแคชของ API การเรียกใช้โดยไม่มีอาร์กิวเมนต์จะแสดงผลลัพธ์เป็นสัญญาสำหรับระเบียนทั้งหมด

ดูรายละเอียดเพิ่มเติมด้านล่าง

match(request, opts) แสดงผลลัพธ์เป็น Promise<BackgroundFetchRecord>
เช่นเดียวกับด้านบน แต่แสดงผลลัพธ์ที่ตรงกันรายการแรก
กิจกรรม
progress เรียกใช้เมื่อมีการเปลี่ยนแปลง uploaded, downloaded, result หรือfailureReason

การติดตามความคืบหน้า

ซึ่งทำได้ผ่านเหตุการณ์ progress โปรดทราบว่า downloadTotal คือค่าที่คุณระบุ หรือ 0 หากคุณไม่ได้ระบุค่า

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}%`);
});

การได้รับคําขอและการตอบกลับ

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 คือ BackgroundFetchRecord และมีลักษณะดังนี้

พร็อพเพอร์ตี้
request Request
คำขอที่ระบุ
responseReady Promise<Response>
คำตอบที่ดึงข้อมูล

การตอบกลับอยู่หลังการสัญญาเนื่องจากอาจยังไม่ได้รับ พรอมต์จะปฏิเสธหากการดึงข้อมูลไม่สำเร็จ

เหตุการณ์ Service Worker

กิจกรรม
backgroundfetchsuccess ดึงข้อมูลทั้งหมดเรียบร้อยแล้ว
backgroundfetchfailure การดึงข้อมูลอย่างน้อย 1 รายการไม่สำเร็จ
backgroundfetchabort การดึงข้อมูลอย่างน้อย 1 รายการไม่สำเร็จ

ซึ่งจะมีประโยชน์ก็ต่อเมื่อคุณต้องการล้างข้อมูลที่เกี่ยวข้องเท่านั้น

backgroundfetchclick ผู้ใช้คลิก UI ความคืบหน้าการดาวน์โหลด

ออบเจ็กต์เหตุการณ์มีข้อมูลต่อไปนี้

พร็อพเพอร์ตี้
registration BackgroundFetchRegistration
เมธอด
updateUI({ title, icons }) ให้คุณเปลี่ยนชื่อ/ไอคอนที่ตั้งไว้ตั้งแต่แรก คุณจะระบุหรือไม่ก็ได้ แต่จะช่วยให้คุณระบุบริบทเพิ่มเติมได้หากจําเป็น คุณดำเนินการนี้ได้ *1 ครั้ง* เท่านั้นในระหว่างกิจกรรม backgroundfetchsuccess และ backgroundfetchfailure

การตอบสนองต่อความสําเร็จ/ความล้มเหลว

เราเห็นเหตุการณ์ progress แล้ว แต่เหตุการณ์นี้จะมีประโยชน์ก็ต่อเมื่อผู้ใช้เปิดหน้าเว็บในเว็บไซต์ของคุณอยู่เท่านั้น ประโยชน์หลักของการดึงข้อมูลในเบื้องหลังคือระบบจะดึงข้อมูลต่อไปหลังจากที่ผู้ใช้ออกจากหน้าเว็บหรือปิดเบราว์เซอร์

หากการดึงข้อมูลในเบื้องหลังเสร็จสมบูรณ์ บริการเวิร์กเกอร์ของคุณจะได้รับเหตุการณ์ backgroundfetchsuccess และ event.registration จะเป็นการลงชื่อสมัครใช้การดึงข้อมูลในเบื้องหลัง

หลังจากเหตุการณ์นี้ คุณจะไม่เข้าถึงคำขอและการตอบกลับที่ดึงข้อมูลไว้ได้อีกต่อไป ดังนั้นหากต้องการเก็บไว้ ให้ย้ายคำขอและการตอบกลับดังกล่าวไปยังที่อื่น เช่น แคช API

เช่นเดียวกับเหตุการณ์ของ Service Worker ส่วนใหญ่ ให้ใช้ event.waitUntil เพื่อให้ Service Worker ทราบว่าเหตุการณ์เสร็จสมบูรณ์แล้ว

เช่น ใน Service Worker

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!' });
  }());
});

ความล้มเหลวอาจเกิดจาก 404 รายการเดียว ซึ่งอาจไม่สำคัญกับคุณ ดังนั้นคุณอาจยังต้องคัดลอกคำตอบบางส่วนลงในแคชตามที่ระบุไว้ข้างต้น

การตอบสนองต่อการคลิก

UI ที่แสดงความคืบหน้าและการดาวน์โหลดผลลัพธ์จะคลิกได้ เหตุการณ์ backgroundfetchclick ใน Service Worker ช่วยให้คุณตอบสนองต่อเหตุการณ์นี้ได้ ดังที่กล่าวไว้ข้างต้น event.registration จะเป็นการดำเนินการดึงข้อมูลการลงทะเบียนในเบื้องหลัง

สิ่งที่ทําได้ทั่วไปกับเหตุการณ์นี้คือการเปิดหน้าต่าง

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

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

แหล่งข้อมูลเพิ่มเติม

การแก้ไข: บทความเวอร์ชันก่อนหน้าเรียกการดึงข้อมูลในเบื้องหลังว่า "มาตรฐานเว็บ" อย่างไม่ถูกต้อง ปัจจุบัน API นี้ไม่ได้อยู่ในมาตรฐาน คุณสามารถดูข้อกําหนดได้ใน WICG เป็นรายงานฉบับร่างของกลุ่มชุมชน