جارٍ العرض

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

حدث الجلب

Browser Support

  • Chrome: 40.
  • Edge: 17.
  • Firefox: 44.
  • Safari: 11.1.

Source

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

يتلقّى معالج fetch جميع الطلبات من أحد التطبيقات، بما في ذلك عناوين URL وعناوين HTTP، ويتيح لمطوّر التطبيق تحديد كيفية معالجتها.

يعمل عامل الخدمة بين العميل والشبكة.

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

self.addEventListener("fetch", event => {
    console.log(`URL requested: ${event.request.url}`);
});

الردّ على طلب

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

للردّ على طلب وارد، اتّصِل بالدالة event.respondWith() من داخل معالج حدث fetch، على النحو التالي:

// fetch event handler in your service worker file
self.addEventListener("fetch", event => {
    const response = .... // a response or a Promise of response
    event.respondWith(response);
});

يجب استدعاء respondWith() بشكل متزامن ويجب عرض عنصر Response. ولكن لا يمكنك استدعاء respondWith() بعد انتهاء معالج أحداث الجلب، مثلاً ضمن استدعاء غير متزامن. إذا كنت بحاجة إلى انتظار الرد الكامل، يمكنك تمرير Promise إلى respondWith() يتم حله باستخدام Response.

إنشاء الردود

بفضل Fetch API، يمكنك إنشاء استجابات HTTP في رمز JavaScript، ويمكن تخزين هذه الاستجابات مؤقتًا باستخدام Cache Storage API وعرضها كما لو كانت واردة من خادم ويب.

لإنشاء ردّ، أنشئ عنصر Response جديدًا، مع ضبط النص والخيارات، مثل الحالة والعناوين:

const simpleResponse = new Response("Body of the HTTP response");

const options = {
   status: 200,
   headers: {
    'Content-type': 'text/html'
   }
};
const htmlResponse = new Response("<b>HTML</b> content", options)

الردّ من ذاكرة التخزين المؤقت

بعد أن تعرّفت على كيفية عرض استجابات HTTP من عامل الخدمة، حان الوقت لاستخدام واجهة Caching Storage لتخزين مواد العرض على الجهاز.

يمكنك استخدام واجهة برمجة التطبيقات الخاصة بتخزين ذاكرة التخزين المؤقت للتحقّق مما إذا كان الطلب الذي تم تلقّيه من تطبيق الويب التقدّمي متوفّرًا في ذاكرة التخزين المؤقت، وإذا كان متوفّرًا، يمكنك الردّ على respondWith() باستخدام هذا الطلب. لإجراء ذلك، عليك أولاً البحث داخل ذاكرة التخزين المؤقت. تبحث الدالة match()، المتوفّرة في واجهة caches ذات المستوى الأعلى، في جميع المتاجر في مصدرك أو في عنصر واحد مفتوح من ذاكرة التخزين المؤقت.

تتلقّى الدالة match() طلب HTTP أو عنوان URL كمعلَمة، وتعرض وعدًا يتم تنفيذه باستخدام الردّ المرتبط بالمفتاح المقابل.

// Global search on all caches in the current origin
caches.match(urlOrRequest).then(response => {
   console.log(response ? response : "It's not in the cache");
});

// Cache-specific search
caches.open("pwa-assets").then(cache => {
  cache.match(urlOrRequest).then(response => {
    console.log(response ? response : "It's not in the cache");
  });
});

استراتيجيات التخزين المؤقت

لا يتناسب عرض الملفات من ذاكرة التخزين المؤقت للمتصفح فقط مع كل حالات الاستخدام. على سبيل المثال، يمكن للمستخدم أو المتصفّح إزالة البيانات من ذاكرة التخزين المؤقت. لهذا السبب، عليك تحديد استراتيجياتك الخاصة لتقديم مواد العرض لتطبيق الويب التقدّمي. لا تقتصر على استراتيجية تخزين مؤقت واحدة. يمكنك تحديد قيم مختلفة لأنماط عناوين URL المختلفة. على سبيل المثال، يمكنك استخدام استراتيجية واحدة للحد الأدنى من مواد عرض واجهة المستخدم، واستراتيجية أخرى لطلبات البيانات من واجهة برمجة التطبيقات، واستراتيجية ثالثة لعناوين URL الخاصة بالصور والبيانات. لإجراء ذلك، اقرأ event.request.url في ServiceWorkerGlobalScope.onfetch وحلِّله باستخدام التعبيرات العادية أو نمط عنوان URL. (في وقت كتابة هذا المقال، لا يتوافق نمط عنوان URL مع جميع المنصات).

في ما يلي الاستراتيجيات الأكثر شيوعًا:

Cache First
يبحث أولاً عن ردّ مخزّن مؤقتًا، ثم يعود إلى الشبكة إذا لم يتم العثور على ردّ.
الشبكة أولاً
يطلب هذا الخيار الحصول على ردّ من الشبكة أولاً، وإذا لم يتم تلقّي أي ردّ، يتم البحث عن ردّ في ذاكرة التخزين المؤقت.
Stale While Revalidate
يعرض ردًا من ذاكرة التخزين المؤقت، بينما يطلب في الخلفية أحدث إصدار ويحفظه في ذاكرة التخزين المؤقت عند طلب الأصل في المرة التالية.
Network-Only
يردّ دائمًا برسالة من الشبكة أو يعرض أخطاء. لا يتم الرجوع إلى ذاكرة التخزين المؤقت مطلقًا.
Cache-Only
يردّ دائمًا باستجابة من ذاكرة التخزين المؤقت أو يعرض خطأ. ولن يتم الرجوع إلى الشبكة أبدًا. يجب إضافة مواد العرض التي سيتم عرضها باستخدام هذه الاستراتيجية إلى ذاكرة التخزين المؤقت قبل طلبها.

ذاكرة التخزين المؤقت أولاً

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

استراتيجية &quot;ذاكرة التخزين المؤقت أولاً&quot;

self.addEventListener("fetch", event => {
   event.respondWith(
     caches.match(event.request)
     .then(cachedResponse => {
       // It can update the cache to serve updated content on the next request
         return cachedResponse || fetch(event.request);
     }
   )
  )
});

الشبكة أولاً

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

استراتيجية &quot;الشبكة أولاً&quot;

self.addEventListener("fetch", event => {
   event.respondWith(
     fetch(event.request)
     .catch(error => {
       return caches.match(event.request) ;
     })
   );
});

Stale while revalidate

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

استراتيجية &quot;البيانات القديمة أثناء إعادة التحقّق&quot;

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request).then(cachedResponse => {
        const networkFetch = fetch(event.request).then(response => {
          // update the cache with a clone of the network response
          const responseClone = response.clone()
          caches.open(url.searchParams.get('name')).then(cache => {
            cache.put(event.request, responseClone)
          })
          return response
        }).catch(function (reason) {
          console.error('ServiceWorker fetch failed: ', reason)
        })
        // prioritize cached response over network
        return cachedResponse || networkFetch
      }
    )
  )
})

الشبكة فقط

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

استراتيجية &quot;الشبكة فقط&quot;

ذاكرة التخزين المؤقت فقط

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

self.addEventListener("fetch", event => {
   event.respondWith(caches.match(event.request));
});

استراتيجية التخزين المؤقت فقط

الاستراتيجيات المخصّصة

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

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

تعديل مواد العرض

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

الموارد