تسريع مشغّل الخدمات باستخدام عمليات التحميل المُسبقة للتنقّل

يتيح لك التحميل المُسبق للتنقّل التغلب على وقت بدء تشغيل مشغّل الخدمات من خلال تقديم طلبات بالتوازي.

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

التوافق مع المتصفح

  • 59
  • 18
  • 99
  • 15.4

المصدر

ملخّص

المشكلة

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

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

لا يمثل وقت بدء التشغيل أي مشكلة إذا كنت تستجيب من ذاكرة التخزين المؤقت، حيث إن فائدة تخطي الشبكة أكبر من تأخير بدء التشغيل. ولكن إذا كنت تستجيب باستخدام الشبكة...

حذاء طويل في جنوب غرب
طلب التنقّل

يتأخر طلب الشبكة بسبب تشغيل عامل الخدمة.

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

لفتت Facebook انتباهنا إلى تأثير هذه المشكلة، وطلبت طريقة لتنفيذ طلبات التنقل بالتوازي:

حذاء طويل في جنوب غرب
طلب التنقّل



وقلنا "نعم، يبدو عادلاً".

"التحميل المسبق للتنقّل" لإنقاذ المنطقة

التحميل المُسبَق للتنقل هو ميزة تتيح لك قول "مرحبًا، عندما يقدم المستخدم طلب تنقُّل عبر GET، يمكنك بدء طلب الشبكة أثناء تشغيل عامل الخدمة".

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

وإليك فيديو لهذه العملية حيث يتم منح عامل الخدمة مهلة متعمّدة في بدء التشغيل لمدة 500 ملي ثانية باستخدام حلقة "أثناء التشغيل":

إليك العرض التوضيحي نفسه. للحصول على مزايا التحميل المُسبق للتنقّل، ستحتاج إلى متصفّح متوافق معها.

تفعيل التحميل المُسبَق للتنقّل

addEventListener('activate', event => {
  event.waitUntil(async function() {
    // Feature-detect
    if (self.registration.navigationPreload) {
      // Enable navigation preloads!
      await self.registration.navigationPreload.enable();
    }
  }());
});

يمكنك الاتصال برقم "navigationPreload.enable()" متى شئت أو إيقافه باستخدام "navigationPreload.disable()". ومع ذلك، بما أنّ حدث fetch يحتاج إلى الاستفادة منه، من الأفضل تفعيله أو إيقافه في حدث activate لعامل الخدمة.

استخدام الردّ المُحمَّل مُسبَقًا

سيُجري المتصفّح الآن عمليات تحميل مسبقة لعمليات الانتقال، ولكن لا يزال عليك استخدام الاستجابة:

addEventListener('fetch', event => {
  event.respondWith(async function() {
    // Respond from the cache if we can
    const cachedResponse = await caches.match(event.request);
    if (cachedResponse) return cachedResponse;

    // Else, use the preloaded response, if it's there
    const response = await event.preloadResponse;
    if (response) return response;

    // Else try the network.
    return fetch(event.request);
  }());
});

يمثّل الرمز event.preloadResponse وعدًا يتم التعامل معه بالردّ في الحالات التالية:

  • تم تفعيل التحميل المسبق للتنقل.
  • الطلب هو طلب GET.
  • الطلب هو طلب تنقّل (يتم إنشاؤه من خلال المتصفّحات عند تحميل الصفحات، بما في ذلك إطارات iframe).

وبخلاف ذلك، تظل السمة event.preloadResponse هناك، ولكن يتم التعامل معها مع السمة undefined.

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

لنفترض أننا أردنا عرض مقالة:

addEventListener('fetch', event => {
  const url = new URL(event.request.url);
  const includeURL = new URL(url);
  includeURL.pathname += 'include';

  if (isArticleURL(url)) {
    event.respondWith(async function() {
      // We're going to build a single request from multiple parts.
      const parts = [
        // The top of the page.
        caches.match('/article-top.include'),
        // The primary content
        fetch(includeURL)
          // A fallback if the network fails.
          .catch(() => caches.match('/article-offline.include')),
        // The bottom of the page
        caches.match('/article-bottom.include')
      ];

      // Merge them all together.
      const {done, response} = await mergeResponses(parts);

      // Wait until the stream is complete.
      event.waitUntil(done);

      // Return the merged response.
      return response;
    }());
  }
});

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

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

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

لإتاحة ذلك، يتم إرسال عنوان مع كل طلب تحميل مُسبَق:

Service-Worker-Navigation-Preload: true

يمكن للخادم استخدام هذا لإرسال محتوى مختلف لطلبات التحميل المسبق للتنقل مقارنةً بطلب التنقل العادي. ما عليك سوى إضافة عنوان Vary: Service-Worker-Navigation-Preload لكي تتعرّف ذاكرات التخزين المؤقت على أنّ ردودك مختلفة.

يمكننا الآن استخدام طلب التحميل المسبق:

// Try to use the preload
const networkContent = Promise.resolve(event.preloadResponse)
  // Else do a normal fetch
  .then(r => r || fetch(includeURL))
  // A fallback if the network fails.
  .catch(() => caches.match('/article-offline.include'));

const parts = [
  caches.match('/article-top.include'),
  networkContent,
  caches.match('/article-bottom')
];

تغيير العنوان

وفقًا للإعدادات التلقائية، تكون قيمة عنوان Service-Worker-Navigation-Preload هي true، ولكن يمكنك ضبطها على أي قيمة تريدها:

navigator.serviceWorker.ready.then(registration => {
  return registration.navigationPreload.setHeaderValue(newValue);
}).then(() => {
  console.log('Done!');
});

يمكنك، على سبيل المثال، تعيينه على معرف آخر مشاركة خزّنتها مؤقتًا محليًا، بحيث يعرض الخادم البيانات الأحدث فقط.

الحصول على الولاية

يمكنك البحث عن حالة التحميل المُسبق للتنقل باستخدام getState:

navigator.serviceWorker.ready.then(registration => {
  return registration.navigationPreload.getState();
}).then(state => {
  console.log(state.enabled); // boolean
  console.log(state.headerValue); // string
});

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

جزء من سلسلة الألعاب القابلة للتشغيل التفاعلي مؤخرًا