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

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

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

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

الواجهة Cache مقابل ذاكرة التخزين المؤقت HTTP

إذا لم يسبق لك استخدام واجهة Cache، قد يكون من المغري اعتبارها بنفس أو على الأقل يتعلق بذاكرة التخزين المؤقت HTTP. لكن الأمر ليس كذلك.

  • الواجهة Cache هي آلية تخزين مؤقت منفصلة تمامًا عن ذاكرة التخزين المؤقت HTTP.
  • أيًا كان Cache-Control ولا تؤثر الإعدادات التي تستخدمها للتأثير في ذاكرة التخزين المؤقت لـ HTTP على مواد العرض التي يتم تخزينها في واجهة Cache.

من المفيد اعتبار ذاكرات التخزين المؤقت للمتصفح على أنها طبقات. ذاكرة التخزين المؤقت لبروتوكول HTTP هي ذاكرة تخزين مؤقت منخفضة المستوى تستند إلى أزواج المفتاح/القيمة مع توجيهات معبر عنها في عناوين HTTP.

في المقابل، واجهة Cache هي ذاكرة تخزين مؤقت عالية المستوى تستند إلى JavaScript API. ويوفر هذا مرونة أكبر مقارنةً باستخدام أزواج المفتاح/القيمة HTTP المبسّطة نسبيًا، وهو نصف ما يجعل استراتيجيات التخزين المؤقت ممكنة. في ما يلي بعض الطرق المهمة لواجهة برمجة التطبيقات في ما يتعلق بذاكرة التخزين المؤقت لمشغِّل الخدمات:

  • CacheStorage.open لإنشاء مثيل Cache جديد.
  • Cache.add وCache.put لتخزين استجابات الشبكة في ذاكرة التخزين المؤقت لمشغِّل الخدمات.
  • Cache.match لتحديد موقع استجابة مخزّنة مؤقتًا في مثيل Cache.
  • Cache.delete لإزالة استجابة مخزّنة مؤقتًا من مثيل Cache.

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

حدث "fetch" المتواضع

النصف الآخر من استراتيجية التخزين المؤقت هو خادم حدث واحد (fetch). سمعتم في هذه الوثائق حتى الآن عن "اعتراض طلبات الشبكة" وحدث fetch داخل مشغّل الخدمات هو المكان الذي يحدث فيه:

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('install', (event) => {
  event.waitUntil(caches.open(cacheName));
});

self.addEventListener('fetch', async (event) => {
  // Is this a request for an image?
  if (event.request.destination === 'image') {
    // Open the cache
    event.respondWith(caches.open(cacheName).then((cache) => {
      // Respond with the image from the cache or from the network
      return cache.match(event.request).then((cachedResponse) => {
        return cachedResponse || fetch(event.request.url).then((fetchedResponse) => {
          // Add the network response to the cache for future visits.
          // Note: we need to make a copy of the response to save it in
          // the cache and use the original as the request response.
          cache.put(event.request, fetchedResponse.clone());

          // Return the network response
          return fetchedResponse;
        });
      });
    }));
  } else {
    return;
  }
});

هذا مثال لعبة - واحدة يمكنك رؤيتها عمليًا، ولكن تقدم لمحة عما يمكن للعاملين في الخدمة القيام به. يؤدي الرمز البرمجي أعلاه إلى تنفيذ ما يلي:

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

يحتوي كائن event في عملية الجلب على الموقع الإلكتروني "request" بعض المعلومات المفيدة لمساعدتك في تحديد نوع كل طلب:

  • url، وهو عنوان URL لطلب الشبكة الذي يعالجه حدث fetch حاليًا.
  • method، وهي طريقة الطلب (على سبيل المثال، GET أو POST).
  • mode، والتي تصف وضع الطلب. يتم استخدام القيمة 'navigate' غالبًا لتمييز طلبات مستندات HTML عن الطلبات الأخرى.
  • destination، الذي يصف نوع المحتوى المطلوب بطريقة تتجنّب استخدام امتداد ملف مادة العرض المطلوبة

نكرّر أنّ "asynchrony" هي اسم اللعبة. ستتذكّر أنّ حدث "install" يقدّم event.waitUntil تتطلب الوعد وتنتظر الحل قبل مواصلة التفعيل. يقدّم الحدث "fetch" عرضًا مشابهًا طريقة واحدة (event.respondWith) يمكنك استخدامها لعرض نتيجة تسلسل هرمي طلب واحد (fetch) أو الاستجابة التي يعرضها واجهة Cache match.

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

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

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

عرض التدفق من الصفحة إلى مشغّل الخدمات إلى ذاكرة التخزين المؤقت

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

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

// Assets to precache
const precachedAssets = [
  '/possum1.jpg',
  '/possum2.jpg',
  '/possum3.jpg',
  '/possum4.jpg'
];

self.addEventListener('install', (event) => {
  // Precache assets on install
  event.waitUntil(caches.open(cacheName).then((cache) => {
    return cache.addAll(precachedAssets);
  }));
});

self.addEventListener('fetch', (event) => {
  // Is this one of our precached assets?
  const url = new URL(event.request.url);
  const isPrecachedRequest = precachedAssets.includes(url.pathname);

  if (isPrecachedRequest) {
    // Grab the precached asset from the cache
    event.respondWith(caches.open(cacheName).then((cache) => {
      return cache.match(event.request.url);
    }));
  } else {
    // Go to the network
    return;
  }
});

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

الشبكة فقط

يعرض التدفق من الصفحة، إلى مشغّل الخدمات، إلى الشبكة.

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

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

ذاكرة التخزين المؤقت أولاً، والرجوع إلى الشبكة

يعرض التدفق من الصفحة إلى مشغّل الخدمات ثم إلى ذاكرة التخزين المؤقت ثم إلى الشبكة إذا لم يكن في ذاكرة التخزين المؤقت.

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

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

في ما يلي مثال على هذه الاستراتيجية، يمكنك اختباره في عرض توضيحي مباشر:

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('fetch', (event) => {
  // Check if this is a request for an image
  if (event.request.destination === 'image') {
    event.respondWith(caches.open(cacheName).then((cache) => {
      // Go to the cache first
      return cache.match(event.request.url).then((cachedResponse) => {
        // Return a cached response if we have one
        if (cachedResponse) {
          return cachedResponse;
        }

        // Otherwise, hit the network
        return fetch(event.request).then((fetchedResponse) => {
          // Add the network response to the cache for later visits
          cache.put(event.request, fetchedResponse.clone());

          // Return the network response
          return fetchedResponse;
        });
      });
    }));
  } else {
    return;
  }
});

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

الشبكة أولاً، الرجوع إلى ذاكرة التخزين المؤقت

عرض التدفق من الصفحة إلى مشغّل الخدمات ثم إلى الشبكة ثم التخزين المؤقت في حال عدم توفُّر الشبكة.

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

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

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

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('fetch', (event) => {
  // Check if this is a navigation request
  if (event.request.mode === 'navigate') {
    // Open the cache
    event.respondWith(caches.open(cacheName).then((cache) => {
      // Go to the network first
      return fetch(event.request.url).then((fetchedResponse) => {
        cache.put(event.request, fetchedResponse.clone());

        return fetchedResponse;
      }).catch(() => {
        // If the network is unavailable, get
        return cache.match(event.request.url);
      });
    }));
  } else {
    return;
  }
});

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

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

بيانات قديمة أثناء إعادة التحقّق

يعرض التدفق من الصفحة إلى مشغّل الخدمات ثم إلى ذاكرة التخزين المؤقت ثم من الشبكة إلى ذاكرة التخزين المؤقت.

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

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

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

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('fetch', (event) => {
  if (event.request.destination === 'image') {
    event.respondWith(caches.open(cacheName).then((cache) => {
      return cache.match(event.request).then((cachedResponse) => {
        const fetchedResponse = fetch(event.request).then((networkResponse) => {
          cache.put(event.request, networkResponse.clone());

          return networkResponse;
        });

        return cachedResponse || fetchedResponse;
      });
    }));
  } else {
    return;
  }
});

يمكنك مشاهدة ذلك عمليًا في المزيد من العروض التوضيحية المباشرة خاصةً إذا كنت تهتم بعلامة تبويب الشبكة في أدوات المطورين بالمتصفّح، وعارض CacheStorage الخاص به (إذا كانت أدوات المطوّرين في متصفّحك تتضمّن مثل هذه الأداة).

ننتقل إلى Workbox!

يختتم هذا المستند مراجعتنا لواجهة برمجة تطبيقات عامل الخدمة، بالإضافة إلى واجهات برمجة التطبيقات ذات الصلة وهذا يعني أنك تعلمت ما يكفي عن كيفية استخدام عاملي الخدمة مباشرةً لبدء إصلاح أداة Workbox!