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

เจค อาร์ชิบาลด์
เจค อาร์ชิบาลด์

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

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

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

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

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

ทดลองการสาธิตด้วยตัวเองและเรียกดูโค้ด

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

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

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

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

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

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

API

การตรวจหาฟีเจอร์

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

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

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

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

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 ล่วงหน้า

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

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

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

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

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

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

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

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

...โดยการส่ง id ของการดึงข้อมูลพื้นหลังที่คุณต้องการ 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" - การดึงข้อมูล 1 ครั้งไม่สำเร็จด้วยสาเหตุอื่น เช่น CORS, MIX, การตอบสนองบางส่วนที่ไม่ถูกต้อง หรือเครือข่ายการดึงข้อมูลที่ไม่สำเร็จซ้ำไม่ได้
  • "quota-exceeded" - ใช้โควต้าพื้นที่เก็บข้อมูลหมดระหว่างการดึงข้อมูลในเบื้องหลัง
  • "download-total-exceeded" - เกินจำนวน "downloadTotal" ที่ระบุ
recordsAvailable boolean
จะเข้าถึงคำขอ/คำตอบที่สำคัญได้ไหม

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

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

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

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

โดยอาร์กิวเมนต์นี้จะเหมือนกับ Cache 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 }) ช่วยให้คุณสามารถเปลี่ยนชื่อ/ไอคอนที่ตั้งค่าไว้ในตอนแรกได้ ขั้นตอนนี้เป็นขั้นตอนที่ไม่บังคับ แต่ช่วยให้คุณให้บริบทเพิ่มเติมหากจำเป็น คุณสามารถทำเช่นนี้ได้เพียง *ครั้งเดียว* ในระหว่างกิจกรรม backgroundfetchsuccess และ backgroundfetchfailure

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

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

หากการดึงข้อมูลในเบื้องหลังเสร็จสมบูรณ์แล้ว Service Worker จะได้รับเหตุการณ์ backgroundfetchsuccess และ event.registration จะเป็นการลงทะเบียนการดึงข้อมูลในเบื้องหลัง

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

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

ตัวอย่างเช่น ใน 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 อาจเกิดจากข้อผิดพลาด 404 รายการซึ่งอาจไม่สำคัญสำหรับคุณ จึงควรคัดลอกคำตอบลงในแคชข้างต้น

การโต้ตอบเพื่อคลิก

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

โดยทั่วไปแล้ว กิจกรรมนี้คือการเปิดหน้าต่าง

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

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

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

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