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

جيك أرشيبالد
جيك أرشيبالد

في عام 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 إذا كان رقم التعريف يطابق عملية جلب حالية في الخلفية.

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

يتيح لك توفير العديد من الطلبات لجلب واحد في الخلفية دمج الأشياء التي تمثل شيئًا واحدًا للمستخدم منطقيًا. على سبيل المثال، يمكن تقسيم فيلم إلى آلاف الموارد (وهو نموذج نموذجي باستخدام 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 كمسودة تقرير لمجموعة المنتدى.