كتاب الطبخ بلا إنترنت

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

للحصول على عرض توضيحي عملي لبعض هذه الأنماط، يمكنك الاطّلاع على محتوى مدرَّب، وهذا الفيديو الذي يعرض تأثير الأداء.

جهاز ذاكرة التخزين المؤقت - وقت تخزين الموارد

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

عند التثبيت - كتبعية

عند التثبيت - كتبعية.
عند التثبيت: كتبعية.

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

مثال مثالي لما يلي: CSS والصور والخطوط وJavaScript والنماذج... وأي شيء يمكن أن تعتبره ثابتًا لذلك "الإصدار" من موقعك الإلكتروني.

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

self.addEventListener('install', function (event) {
  event.waitUntil(
    caches.open('mysite-static-v3').then(function (cache) {
      return cache.addAll([
        '/css/whatever-v3.css',
        '/css/imgs/sprites-v6.png',
        '/css/fonts/whatever-v8.woff',
        '/js/all-min-v4.js',
        // etc.
      ]);
    }),
  );
});

يحتاج event.waitUntil إلى وعد بتحديد مدة التثبيت ومدى نجاحه. إذا تم رفض الوعد، سيعتبر التثبيت إخفاقًا وسيتم إلغاء "مشغّل الخدمات" هذا (إذا كان إصدار أقدم قيد التشغيل، سيظل سليمًا). وعودك بإرجاع المشتريات من caches.open() وcache.addAll() وفي حال تعذّر استرجاع أي من الموارد، سيتم رفض استدعاء cache.addAll().

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

عند التثبيت، وليس كتبعية

عند التثبيت - وليس كتبعية.
عند التثبيت، وليس كتبعية.

يشبه ذلك ما سبق، ولكن لن يؤخر اكتمال التثبيت ولن يتسبب في إخفاق التثبيت في حالة إخفاق التخزين المؤقت.

مثالي: الموارد الكبيرة غير المطلوبة على الفور، مثل مواد العرض للمستويات اللاحقة من اللعبة.

self.addEventListener('install', function (event) {
  event.waitUntil(
    caches.open('mygame-core-v1').then(function (cache) {
      cache
        .addAll
        // levels 11–20
        ();
      return cache
        .addAll
        // core assets and levels 1–10
        ();
    }),
  );
});

في المثال أعلاه، لا يتمكّن من إكمال وعد cache.addAll الخاص بالمستويات من 11 إلى 20 في event.waitUntil، لذلك حتى في حال تعذّر الوصول إلى اللعبة، ستظل اللعبة متاحة بلا اتصال بالإنترنت. بالطبع، سيتعين عليك مراعاة الغياب المحتمل لهذه المستويات وإعادة محاولة تخزينها مؤقتًا إذا كانت مفقودة.

قد يؤدي ذلك إلى قتل عامل الخدمة أثناء تنزيل المستويات من 11 إلى 20 نظرًا لانتهاء معالجة الأحداث، مما يعني أنه لن يتم تخزينها مؤقتًا. في المستقبل، ستعالج واجهة برمجة التطبيقات Web Periodic Background Synchronization API حالات مماثلة بالإضافة إلى عمليات التنزيل الأكبر حجمًا مثل الأفلام. لا تتوافق واجهة برمجة التطبيقات هذه حاليًا إلا مع شوك Chromium.

عند التفعيل

عند التفعيل.
عند التفعيل.

مثالي لإجراء ما يلي: الإزالة والنقل.

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

self.addEventListener('activate', function (event) {
  event.waitUntil(
    caches.keys().then(function (cacheNames) {
      return Promise.all(
        cacheNames
          .filter(function (cacheName) {
            // Return true if you want to remove this cache,
            // but remember that caches are shared across
            // the whole origin
          })
          .map(function (cacheName) {
            return caches.delete(cacheName);
          }),
      );
    }),
  );
});

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

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

عند تفاعل المستخدم

على تفاعل المستخدم.
حول تفاعل المستخدم:

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

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

document.querySelector('.cache-article').addEventListener('click', function (event) {
  event.preventDefault();

  var id = this.dataset.articleId;
  caches.open('mysite-article-' + id).then(function (cache) {
    fetch('/get-article-urls?id=' + id)
      .then(function (response) {
        // /get-article-urls returns a JSON-encoded array of
        // resource URLs that a given article depends on
        return response.json();
      })
      .then(function (urls) {
        cache.addAll(urls);
      });
  });
});

تتوفر caches API من الصفحات وعمال الخدمات، ما يعني أنه يمكنك الإضافة إلى ذاكرة التخزين المؤقت مباشرة من الصفحة.

استجابة الشبكة

عند الاستجابة للشبكة.
استجابة للشبكة:

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

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

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

self.addEventListener('fetch', function (event) {
  event.respondWith(
    caches.open('mysite-dynamic').then(function (cache) {
      return cache.match(event.request).then(function (response) {
        return (
          response ||
          fetch(event.request).then(function (response) {
            cache.put(event.request, response.clone());
            return response;
          })
        );
      });
    }),
  );
});

للسماح باستخدام الذاكرة بشكل فعّال، يمكنك قراءة نص الاستجابة/الطلب مرة واحدة فقط. يستخدم الرمز أعلاه .clone() لإنشاء نُسخ إضافية يمكن قراءتها بشكل منفصل.

في نظام trained-to-thrill أستخدم هذا كتخزين لصور فوتوري المخزن مؤقتًا.

إعادة إثبات الملكية القديمة

جارٍ إعادة إثبات الملكية.
المحتوى القديم أثناء إعادة التحقق:

مثالي: يتم تحديث الموارد بشكل متكرر حيث لا يلزم الحصول على أحدث إصدار. يمكن أن تندرج الصور الرمزية ضمن هذه الفئة.

وفي حال توفُّر نسخة مخزَّنة مؤقتًا، يمكنك استخدامها، ولكن يمكنك جلب تحديث في المرة القادمة.

self.addEventListener('fetch', function (event) {
  event.respondWith(
    caches.open('mysite-dynamic').then(function (cache) {
      return cache.match(event.request).then(function (response) {
        var fetchPromise = fetch(event.request).then(function (networkResponse) {
          cache.put(event.request, networkResponse.clone());
          return networkResponse;
        });
        return response || fetchPromise;
      });
    }),
  );
});

ويشبه هذا إلى حد كبير عملية التحقق القديمة أثناء إعادة التحقق في بروتوكول HTTP.

عند إرسال الرسائل الفورية

في الرسائل الفورية
في الرسائل الفورية:

Push API هي ميزة أخرى تم تصميمها استنادًا إلى "Service Worker". ويتيح ذلك لعامل الخدمة أن يتم تنشيطه استجابةً لرسالة واردة من خدمة المراسلة في نظام التشغيل. ويحدث ذلك حتى عندما لا تكون لدى المستخدم علامة تبويب مفتوحة على موقعك الإلكتروني. لا يتم تنشيط سوى عامل الخدمة. إذا كنت تطلب الإذن لتنفيذ هذا الإجراء من إحدى الصفحات، سيُطلب منك ذلك.

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

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

يحدّث هذا الرمز ذاكرات التخزين المؤقت قبل عرض إشعار:

self.addEventListener('push', function (event) {
  if (event.data.text() == 'new-email') {
    event.waitUntil(
      caches
        .open('mysite-dynamic')
        .then(function (cache) {
          return fetch('/inbox.json').then(function (response) {
            cache.put('/inbox.json', response.clone());
            return response.json();
          });
        })
        .then(function (emails) {
          registration.showNotification('New email', {
            body: 'From ' + emails[0].from.name,
            tag: 'new-email',
          });
        }),
    );
  }
});

self.addEventListener('notificationclick', function (event) {
  if (event.notification.tag == 'new-email') {
    // Assume that all of the resources needed to render
    // /inbox/ have previously been cached, e.g. as part
    // of the install handler.
    new WindowClient('/inbox/');
  }
});

عند المزامنة في الخلفية

عند المزامنة في الخلفية.
عند تفعيل المزامنة في الخلفية

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

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

self.addEventListener('sync', function (event) {
  if (event.id == 'update-leaderboard') {
    event.waitUntil(
      caches.open('mygame-dynamic').then(function (cache) {
        return cache.add('/leaderboard.json');
      }),
    );
  }
});

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

يتم منح الأصل قدرًا معينًا من المساحة الخالية لتنفيذ المهام المطلوبة. وتكون هذه المساحة الخالية مشتركة بين كل مساحة تخزين المصدر: مساحة التخزين(المحلي) وIndexedDB والوصول إلى نظام الملفات وبالطبع ذاكرات التخزين المؤقت.

المبلغ الذي ستحصل عليه غير مطابق للمواصفات. وستختلف حسب الجهاز وحالة مساحة التخزين. يمكنك معرفة المبلغ الذي حصلت عليه من خلال:

navigator.storageQuota.queryInfo('temporary').then(function (info) {
  console.log(info.quota);
  // Result: <quota in bytes>
  console.log(info.usage);
  // Result: <used data in bytes>
});

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

لحلّ هذه المشكلة، استخدِم واجهة StorageManager:

// From a page:
navigator.storage.persist()
.then(function(persisted) {
  if (persisted) {
    // Hurrah, your data is here to stay!
  } else {
   // So sad, your data may get chucked. Sorry.
});

بالطبع، يجب على المستخدم منح الإذن. لإجراء ذلك، استخدِم واجهة برمجة التطبيقات Permissions API.

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

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

عرض الاقتراحات - الاستجابة للطلبات

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

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

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

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

self.addEventListener('fetch', function (event) {
  // If a match isn't found in the cache, the response
  // will look like a connection error
  event.respondWith(caches.match(event.request));
});

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

الشبكة فقط

الشبكة فقط.
الشبكة فقط

مثالي: العناصر التي لا تتوفّر لها مكافئة بلا إنترنت، مثل إشعارات الإحصاءات، وطلبات ليست ذات صلة بحالات GET.

self.addEventListener('fetch', function (event) {
  event.respondWith(fetch(event.request));
  // or simply don't call event.respondWith, which
  // will result in default browser behavior
});

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

ذاكرة التخزين المؤقت، جارٍ استخدام الشبكة مجددًا

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

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

self.addEventListener('fetch', function (event) {
  event.respondWith(
    caches.match(event.request).then(function (response) {
      return response || fetch(event.request);
    }),
  );
});

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

ذاكرة التخزين المؤقت وسباق الشبكة

ذاكرة التخزين المؤقت وسباق الشبكة.
ذاكرة التخزين المؤقت وسباق الشبكة.

مثالي: مواد العرض الصغيرة التي تسعى إلى الحفاظ على أدائها على الأجهزة التي تعاني من بطء في الوصول إلى القرص.

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

// Promise.race is no good to us because it rejects if
// a promise rejects before fulfilling. Let's make a proper
// race function:
function promiseAny(promises) {
  return new Promise((resolve, reject) => {
    // make sure promises are all promises
    promises = promises.map((p) => Promise.resolve(p));
    // resolve this promise as soon as one resolves
    promises.forEach((p) => p.then(resolve));
    // reject if all promises reject
    promises.reduce((a, b) => a.catch(() => b)).catch(() => reject(Error('All failed')));
  });
}

self.addEventListener('fetch', function (event) {
  event.respondWith(promiseAny([caches.match(event.request), fetch(event.request)]));
});

إعادة تخزين الشبكة إلى ذاكرة التخزين المؤقت

تتم إعادة تخزين الشبكة إلى ذاكرة التخزين المؤقت.
عودة الشبكة إلى ذاكرة التخزين المؤقت.

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

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

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

self.addEventListener('fetch', function (event) {
  event.respondWith(
    fetch(event.request).catch(function () {
      return caches.match(event.request);
    }),
  );
});

ذاكرة التخزين المؤقت ثم الشبكة

ذاكرة التخزين المؤقت ثم الشبكة.
ذاكرة التخزين المؤقت ثم الشبكة

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

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

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

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

الرمز في الصفحة:

var networkDataReceived = false;

startSpinner();

// fetch fresh data
var networkUpdate = fetch('/data.json')
  .then(function (response) {
    return response.json();
  })
  .then(function (data) {
    networkDataReceived = true;
    updatePage(data);
  });

// fetch cached data
caches
  .match('/data.json')
  .then(function (response) {
    if (!response) throw Error('No data');
    return response.json();
  })
  .then(function (data) {
    // don't overwrite newer network data
    if (!networkDataReceived) {
      updatePage(data);
    }
  })
  .catch(function () {
    // we didn't get cached data, the network is our last hope:
    return networkUpdate;
  })
  .catch(showErrorMessage)
  .then(stopSpinner);

الرمز في "مشغّل الخدمات":

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

self.addEventListener('fetch', function (event) {
  event.respondWith(
    caches.open('mysite-dynamic').then(function (cache) {
      return fetch(event.request).then(function (response) {
        cache.put(event.request, response.clone());
        return response;
      });
    }),
  );
});

في قسم تدريب على التشويق، حللت هذه الميزة باستخدام XHR بدلاً من الاسترجاع، وإساءة استخدام العنوان "قبول" لإبلاغ عامل الخدمة بمكان الحصول على النتيجة من (رمز الصفحة ورمز عامل الخدمة).

احتياطي عام

عنصر احتياطي عام.
إجراء احتياطي عام

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

مثالي: الصور الثانوية، مثل الصور الرمزية وطلبات POST التي تعذّر إجراؤها وصفحة "غير متاح أثناء وضع عدم الاتصال".

self.addEventListener('fetch', function (event) {
  event.respondWith(
    // Try the cache
    caches
      .match(event.request)
      .then(function (response) {
        // Fall back to network
        return response || fetch(event.request);
      })
      .catch(function () {
        // If both fail, show a generic fallback:
        return caches.match('/offline.html');
        // However, in reality you'd have many different
        // fallbacks, depending on URL and headers.
        // Eg, a fallback silhouette image for avatars.
      }),
  );
});

من المرجح أن يكون العنصر الذي ترجع إليه تبعية للتثبيت.

إذا كانت صفحتك تنشر رسالة إلكترونية، قد يعود عامل الخدمة إلى تخزين الرسالة الإلكترونية في "البريد الصادر" في IndexedDB والاستجابة من خلال إعلام الصفحة بتعذُّر الإرسال مع الاحتفاظ بالبيانات بنجاح.

استخدام النماذج من جهة عامل الخدمة

النماذج من جهة ServiceWorkers.
نماذج من جهة ServiceWorker:

مثالي لهذه الصفحات: الصفحات التي لا يمكن تخزين استجابة خادمها مؤقتًا.

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

importScripts('templating-engine.js');

self.addEventListener('fetch', function (event) {
  var requestURL = new URL(event.request.url);

  event.respondWith(
    Promise.all([
      caches.match('/article-template.html').then(function (response) {
        return response.text();
      }),
      caches.match(requestURL.path + '.json').then(function (response) {
        return response.json();
      }),
    ]).then(function (responses) {
      var template = responses[0];
      var data = responses[1];

      return new Response(renderTemplate(template, data), {
        headers: {
          'Content-Type': 'text/html',
        },
      });
    }),
  );
});

خلاصة ما سبق ذكره

لست مقيدًا بأي من هذه الطرق. في الواقع، من المحتمل أن تستخدم العديد منها استنادًا إلى عنوان URL للطلب. على سبيل المثال، يستخدم النوع مدرب على التشويق ما يلي:

ما عليك سوى الاطّلاع على الطلب وتحديد ما يجب فعله:

self.addEventListener('fetch', function (event) {
  // Parse the URL:
  var requestURL = new URL(event.request.url);

  // Handle requests to a particular host specifically
  if (requestURL.hostname == 'api.example.com') {
    event.respondWith(/* some combination of patterns */);
    return;
  }
  // Routing for local URLs
  if (requestURL.origin == location.origin) {
    // Handle article URLs
    if (/^\/article\//.test(requestURL.pathname)) {
      event.respondWith(/* some other combination of patterns */);
      return;
    }
    if (/\.webp$/.test(requestURL.pathname)) {
      event.respondWith(/* some other combination of patterns */);
      return;
    }
    if (request.method == 'POST') {
      event.respondWith(/* some other combination of patterns */);
      return;
    }
    if (/cheese/.test(requestURL.pathname)) {
      event.respondWith(
        new Response('Flagrant cheese error', {
          status: 512,
        }),
      );
      return;
    }
  }

  // A sensible default pattern
  event.respondWith(
    caches.match(event.request).then(function (response) {
      return response || fetch(event.request);
    }),
  );
});

...إليك الصورة.

المساهمون

...للرموز الرائعة:

وبفضل جيف بوسنيك لاكتشافه الكثير من الأخطاء التي تظهر قبل النقر على "نشر".

محتوى إضافي للقراءة