لمحة عن ميزة "الجلب في الخلفية"

في عام 2015، أطلقنا ميزة "المزامنة في الخلفية" التي تسمح لعامل الخدمة بالتريث في تنفيذ المهام إلى أن يتصل المستخدم بالإنترنت. وهذا يعني أنّه يمكن للمستخدم كتابة رسالة والنقر على "إرسال" ومغادرة الموقع الإلكتروني مع العِلم أنّه سيتم إرسال الرسالة الآن أو عندما يصبح الاتصال بالإنترنت متاحًا.

وهي ميزة مفيدة، ولكنها تتطلب أن يكون عامل الخدمة نشطًا طوال مدة الجلب. لا يشكّل ذلك مشكلة في المهام القصيرة، مثل إرسال رسالة، ولكن إذا استغرقت المهمة وقتًا طويلاً جدًا، سيوقف المتصفّح الخدمة العاملة، وإلا سيشكّل ذلك خطرًا على خصوصية المستخدم و البطارية.

ماذا لو كنت بحاجة إلى تنزيل محتوى قد يستغرق وقتًا طويلاً، مثل فيلم أو ملفات بودكاست أو مستويات لعبة؟ وهذا هو الغرض من ميزة أداة استرجاع البيانات في الخلفية.

تتوفّر ميزة "جلب البيانات في الخلفية" تلقائيًا منذ الإصدار 74 من Chrome.

في ما يلي عرض توضيحي سريع مدته دقيقتان يعرض الحالة التقليدية للتطبيقات مقارنةً باستخدام ميزة "جلب البيانات في الخلفية":

جرِّب العرض التوضيحي بنفسك وتصفّح الرمز.

آلية العمل

تعمل ميزة "استرداد البيانات في الخلفية" على النحو التالي:

  1. يمكنك أن تطلب من المتصفّح تنفيذ مجموعة من عمليات الجلب في الخلفية.
  2. يجلب المتصفح تلك الأشياء، ويعرض التقدم للمستخدم.
  3. بعد اكتمال عملية الجلب أو تعذّرها، يفتح المتصفّح الخدمة العاملة ويُطلق حدثًا لإعلامك بما حدث. يمكنك هنا تحديد ما إذا كنت تريد اتّخاذ أي إجراء بشأن الردود.

إذا أغلق المستخدم صفحات موقعك الإلكتروني بعد الخطوة 1، لا بأس بذلك، سيستمر التنزيل. ونظرًا لأن عملية الجلب مرئية بدرجة عالية ويمكن إيقافها بسهولة، فلا داعي للقلق بشأن الخصوصية في مهمة المزامنة في الخلفية التي تكون طويلة جدًا. وبما أنّ الخدمة العاملة لا تعمل باستمرار، لا داعي للقلق بشأن إمكانية إساءة استخدام النظام، مثل تعدين عملة البيتكوين في الخلفية.

في بعض الأنظمة الأساسية (مثل Android)، من الممكن أن يتم إغلاق المتصفّح بعد الخطوة 1، لأنّ المتصفّح يمكنه تسليم عملية الجلب إلى نظام التشغيل.

إذا بدأ المستخدم عملية التنزيل بلا اتصال بالإنترنت أو انقطع اتصاله بالإنترنت خلال عملية التنزيل، سيتم إيقاف عملية جلب البيانات في الخلفية مؤقتًا واستئنافها لاحقًا.

واجهة برمجة التطبيقات

ميزة "اكتشاف المحتوى"

وكما هو الحال مع أي ميزة جديدة، عليك معرفة ما إذا كان المتصفّح متوافقًا معها. بالنسبة إلى ميزة "الاسترجاع في الخلفية"، يمكنك تنفيذ الخطوات التالية:

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

بدء استرجاع البيانات في الخلفية

تتأثر واجهة برمجة التطبيقات الرئيسية بتسجيل مشغّل الخدمات، لذا احرص على تسجيل مشغّل الخدمات أولاً. بعد ذلك:

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 ثلاث وسيطات:

المعلمات
id يحدِّد string
بشكلٍ فريد عملية الجلب هذه التي تعمل في الخلفية.

سيرفض backgroundFetch.fetch الطلب إذا كان رقم التعريف يتطابق مع عملية جلب حالية في background.

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
الحجم الإجمالي لنصّ الردود (بعد إزالة ضغطها باستخدام برنامج gzip)

وعلى الرغم من أنّ هذه الخطوة اختيارية، إلا أنّنا ننصحك بشدة بتقديمه. ويُستخدَم لإعلام المستخدم بحجم عملية التنزيل وتقديم معلومات عن مستوى التقدّم. وفي حال عدم تقديم هذه المعلومات، سيُعلم المتصفّح المستخدم بأنّ الحجم غير معروف، ونتيجةً لذلك، قد يلغي المستخدم عملية التنزيل.

إذا تجاوز عدد عمليات تنزيل البيانات في الخلفية العدد المحدَّد هنا، سيتم إلغاء هذه العمليات. ولا بأس على الإطلاق إذا كانت حجم عملية التنزيل أصغر من 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
القيمة التي تم تقديمها عند تسجيل عملية استرجاع البيانات في الخلفية، أو القيمة صفر.
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>
إيقاف جلب البيانات في الخلفية

يتم حلّ الوعد المُعاد بقيمة صحيحة إذا تم إيقاف عملية الجلب بنجاح.

matchAll(request, opts) مرتجعات Promise<Array<BackgroundFetchRecord>>
الحصول على الطلبات والردود

وسيطات الطلبات هنا هي نفسها في واجهة برمجة التطبيقات للمخزن المؤقت. يؤدي الطلب بدون وسيطات إلى عرض وعد لجميع السجلات.

انظر أدناه للحصول على مزيد من التفاصيل.

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>
الردّ الذي تم استرجاعه

تأخّر الردّ عن الموعد المحدّد لأنّه قد لا يكون قد تم استلامه بعد. سيتم رفض الوعد في حال تعذّر استرجاع البيانات.

أحداث مشغِّل الخدمة

الفعاليات
backgroundfetchsuccess تم استرجاع كل البيانات بنجاح.
backgroundfetchfailure تعذّر جلب بيانات واحدة أو أكثر.
backgroundfetchabort تعذّر جلب بيانات واحدة أو أكثر.

لا يكون هذا مفيدًا إلا إذا كنت تريد تنظيف البيانات ذات الصلة.

backgroundfetchclick نقر المستخدم على واجهة مستخدم تقدّم عملية التنزيل.

تتضمّن عناصر الأحداث ما يلي:

أماكن إقامة
registration BackgroundFetchRegistration
الطُرق
updateUI({ title, icons }) يتيح لك تغيير العنوان أو الرموز التي ضبطتها في البداية. هذه الخطوة اختيارية، ولكنها تتيح لك تقديم المزيد من السياق إذا لزم الأمر. يمكنك إجراء ذلك *مرة واحدة* فقط أثناء حدثَي backgroundfetchsuccess وbackgroundfetchfailure.

التفاعل مع حالات النجاح أو الفشل

سبق أن رأينا حدث progress، إلا أنّ ذلك يكون مفيدًا فقط عندما يكون لدى المستخدم صفحة مفتوحة لموقعك الإلكتروني. تتمثل الفائدة الرئيسية من ميزة "الاسترداد في الخلفية" في مواصلة عمل التطبيقات بعد مغادرة المستخدم الصفحة أو حتى إغلاق المتصفّح.

إذا اكتملت عملية استرجاع البيانات في الخلفية بنجاح، سيتلقّى عامل الخدمة حدث backgroundfetchsuccess وسيكون event.registration هو تسجيل الاسترجاع في الخلفية.

بعد هذا الحدث، لن يعود بإمكانك الوصول إلى الطلبات والردود التي تم جلبها، لذا إذا أردت الاحتفاظ بها، عليك نقلها إلى مكان آخر، مثل cache API.

كما هو الحال مع معظم أحداث مشغّلي الخدمات، يمكنك استخدام event.waitUntil حتى يعرف عامل الخدمة عند اكتمال الحدث.

على سبيل المثال، في مشغّل الخدمات:

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 واحدة، والتي ربما لم تكن مهمة بالنسبة إليك، لذلك ربما لا يزال من المفيد نسخ بعض الردود إلى ذاكرة تخزين مؤقت على النحو الوارد أعلاه.

التفاعل مع النقرات

واجهة المستخدم التي تعرض مستوى تقدم التنزيل والنتيجة قابلة للنقر يتيح لك الحدث backgroundfetchclick في مشغّل الخدمات التفاعل مع هذا الإجراء. كما هو موضّح أعلاه، سيكون event.registration هو عملية تسجيل الحصول على البيانات في الخلفية.

الإجراء الشائع لتنفيذ هذا الحدث هو فتح نافذة:

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

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

مراجع إضافية

تصحيح: أشار إصدار سابق من هذه المقالة بشكل خاطئ إلى ميزة "جلب التطبيقات في الخلفية" على أنّها "معيار ويب". لا تتوفّر واجهة برمجة التطبيقات حاليًا على مسار المعايير، ويمكنك الاطّلاع على المواصفات في WICG كمسودة تقرير مجموعة المنتدى.