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

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

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

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

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

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

مناسب لـ: 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().

في trained-to-thrill، أستخدم هذا الإجراء لتخزين مواد العرض الثابتة في ذاكرة التخزين المؤقت.

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

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

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

مناسبة لـ: الموارد الأكبر حجمًا التي لا تكون مطلوبة على الفور، مثل مواد العرض للمستويات اللاحقة في اللعبة

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، لذا حتى إذا تعذّر إكماله، ستظل اللعبة متاحة بلا إنترنت. بالطبع، عليك مراعاة احتمال عدم توفّر هذه المستويات وإعادة محاولة خزنها في حال عدم توفّرها.

قد يتم إيقاف Worker Service أثناء تنزيل المستويات من 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، في قائمة انتظار، لذا قد يؤدي التفعيل لفترة طويلة إلى حظر تحميل الصفحات. يجب أن يكون التفعيل بسيطًا قدر الإمكان، ولا تستخدِمه إلا لتنفيذ الإجراءات التي لم تتمكّن من تنفيذها عندما كان الإصدار القديم مفعّلاً.

في trained-to-thrill، أستخدم هذا الإجراء لإزالة ذاكرات التخزين المؤقت القديمة.

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

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

مناسب لـ: عندما لا يمكن إزالة الموقع الإلكتروني بالكامل من الإنترنت، واخترت السماح للمستخدم باختيار المحتوى الذي يريده متاحًا بلا إنترنت. على سبيل المثال، فيديو على YouTube أو مقالة على هٰذه الصفحة: Wikipedia أو معرض معيّن على Flickr

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

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، أستخدم هذا الإجراء لتخزين صور Flickr.

Stale-while-revalidate

Stale-while-revalidate
Stale-while-revalidate:

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

إذا كان هناك إصدار محفوظ مؤقتًا متاحًا، استخدِمه، ولكن ابحث عن تحديث في المرة القادمة.

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;
     
});
   
}),
 
);
});

يشبه ذلك إلى حد كبير stale-while-revalidate في HTTP.

في رسالة الإشعار الفوري

في رسالة الإشعار الفوري
في رسالة الإشعار الفوري:

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

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

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

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

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/');
 
}
});

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

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

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

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

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، إذن الوصول إلى نظام الملفات، وبالطبع المخازن المؤقتة.

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

if (navigator.storage && navigator.storage.estimate) {
 
const quota = await navigator.storage.estimate();
 
// quota.usage -> Number of bytes used.
 
// quota.quota -> Maximum number of bytes available.
 
const percentageUsed = (quota.usage / quota.quota) * 100;
  console
.log(`You've used ${percentageUsed}% of the available storage.`);
 
const remaining = quota.quota - quota.usage;
  console
.log(`You can write up to ${remaining} more 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));
});

…على الرغم من أنّك لا تحتاج في أغلب الأحيان إلى التعامل مع هذا الموقف تحديدًا، فإنّ Cache، الرجوع إلى الشبكة يتناول هذا الموقف.

الشبكة فقط

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

مناسب لـ: العناصر التي لا تتوفّر لها مكافئة بلا إنترنت، مثل إشعارات الإحصاءات وطلبات 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
});

…على الرغم من أنّك لا تحتاج في أغلب الأحيان إلى التعامل مع هذا الموقف تحديدًا، فإنّ Cache، الرجوع إلى الشبكة يتناول هذا الموقف.

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

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

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

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 يحافظ في الغالب على ترتيب خطي للمحتوى. لقد نسخت هذا النمط لقناتي trained-to-thrill لعرض المحتوى على الشاشة في أسرع وقت ممكن، مع عرض المحتوى الجديد فور وصوله.

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

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;
     
});
   
}),
 
);
});

في trained-to-thrill، تجنّبت هذه المشكلة باستخدام XHR بدلاً من fetch، واستعرَضت لعنوان Accept لإخبار مشغّل الخدمات من أين يمكنه الحصول على النتيجة (رمز الصفحة، رمز مشغّل الخدمات).

الإجراء الاحتياطي العام

الإجراء الاحتياطي العام
العنصر البديل العام:

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

مناسب لـ: الصور الثانوية، مثل الصور الرمزية وطلبات 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 ويردّ على الصفحة بأنّ الإرسال تعذّر ولكن تم الاحتفاظ بالبيانات بنجاح.

إنشاء النماذج من جهة الخدمة

إنشاء النماذج من جهة ServiceWorker
النماذج من جهة 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 للطلب. على سبيل المثال، يستخدم trained-to-thrill ما يلي:

ما عليك سوى الاطّلاع على الطلب وتحديد الإجراء الذي تريد اتّخاذه:

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);
   
}),
 
);
});

…وهكذا.

المساهمون

…للحصول على الرموز الجميلة:

شكرًا لجيفري بوسنيك على رصد العديد من الأخطاء المزعجة قبل أن أضغط على "نشر".

مراجع إضافية