ऑफ़लाइन कुकबुक

Jake Archibald
Jake Archibald

Service Worker की मदद से, हमने ऑफ़लाइन काम करने की समस्या को हल करने की कोशिश बंद कर दी है. साथ ही, डेवलपर को इस समस्या को खुद हल करने के लिए, ज़रूरी जानकारी दी है. इससे आपको कैश मेमोरी में सेव होने और अनुरोधों को मैनेज करने का कंट्रोल मिलता है. इसका मतलब है कि आपको अपने हिसाब से पैटर्न बनाने का विकल्प मिलता है. आइए, अलग-अलग पैटर्न पर एक नज़र डालते हैं. हालांकि, आम तौर पर यूआरएल और संदर्भ के आधार पर, इनमें से कई पैटर्न का एक साथ इस्तेमाल किया जाता है.

इनमें से कुछ पैटर्न के काम करने के तरीके के बार�� में जानने के लिए, ट्रेनिंग और परफ़ॉर्मेंस पर पड़ने वाले असर के बारे में बताने वाला यह वीडियो देखें.

कैश मेमोरी मशीन—संसाधनों को कब सेव करना है

सर्विस वर्कर, आपको अनुरोधों को कैश मेमोरी से अलग मैनेज करने की सुविधा देता है. इसलिए, मैं उन्हें अलग से दिखाऊंगा. सबसे पहले, कैश मेमोरी का इस्तेमाल कब किया जाना चाहिए?

इंस्टॉल करने पर—डिपेंडेंसी के तौर पर

इंस्टॉल करने पर - डिपेंडेंसी के तौर पर.
इंस्टॉल किए जाने पर - डिपेंडेंसी के तौर पर.

सर्विस वर्कर, आपको एक install इवेंट देता है. इसका इस्तेमाल, ऐसे आइटम तैयार करने के लिए किया जा सकता है जिन्हें अन्य इवेंट को मैनेज करने से पहले तैयार करना ज़रूरी है. ऐसा होने पर, आपके Service Worker का कोई भी पिछला वर्शन अब भी चल रहा है और पेज दिखा रहा है. इसलिए, यहां किए गए बदलावों से उस पर कोई असर नहीं पड़ना चाहिए.

इनके लिए सही है: सीएसएस, इमेज, फ़ॉन्ट, JS, टेंप्लेट… यानी आपकी साइट के उस "वर्शन" के लिए, जो स्टैटिक है.

अगर ये फ़ेच नहीं हो पाते हैं, तो आपकी साइट पूरी तरह से काम नहीं करेगी. ये ऐसी चीज़ें हैं जिन्हें प्लैटफ़ॉर्म के हिसाब से बने ऐप्लिकेशन, शुरुआती डाउनलोड में शामिल करते हैं.

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 से event.waitUntil के लिए, 11 से 20 लेवल के गेम को ऑफ़लाइन उपलब्ध कराने का वादा नहीं किया गया है. इसलिए, भले ही यह वादा पूरा न हो पाए, फिर भी गेम ऑफ़लाइन उपलब्ध रहेगा. हालांकि, आपको उन लेवल के मौजूद न होने की संभावना को ध्यान में रखना होगा. अगर वे मौजूद नहीं हैं, तो उन्हें फिर से कैश मेमोरी में सेव करने की कोशिश करें.

लेवल 11 से 20 डाउनलोड होने के दौरान, Service Worker को बंद किया जा सकता है. ऐसा इसलिए, क्योंकि इवेंट मैनेज करने की प्रोसेस पूरी हो चुकी है. इसका मतलब है कि इवेंट कैश मेमोरी में सेव नहीं किए जाएंगे. आने वाले समय में, वेब पीरियडिक बैकग्राउंड सिंक एपीआई, इस तरह के मामलों और फ़िल्मों जैसे बड़े डाउनलोड को मैनेज करेगा. फ़िलहाल, यह एपीआई सिर्फ़ 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);
      });
  });
});

कैश मेमोरी एपीआई, पेजों के साथ-साथ सर्विस वर्कर के लिए भी उपलब्ध है. इसका मतलब है कि सीधे पेज से कैश मेमोरी में जोड़ा जा सकता है.

नेटवर्क रिस्पॉन्स पर

नेटवर्क से जवाब दिया गया.
नेटवर्क के जवाब पर.

इनके लिए सही है: अक्सर अपडेट होने वाले संसाधन, जैसे कि उपयोगकर्ता का इनबॉक्स या लेख का कॉन्टेंट. यह सुविधा, अवतार जैसे ग़ैर-ज़रूरी कॉन्टेंट के लिए भी काम की है. हालांकि, इसका इस्तेमाल करते समय सावधानी बरतने की ज़रूरत है.

अगर कोई अनुरोध कैश मेमोरी में मौजूद किसी भी चीज़ से मेल नहीं खाता है, तो उसे नेटवर्क से पाएं, पेज पर भेजें, और एक ही समय पर उसे कैश मेमोरी में जोड़ें.

अगर आपको अवतार जैसे कई यूआरएल के लिए ऐसा करना है, तो आपको ध्यान रखना होगा कि आपके ओरिजिन के स्टोरेज में जगह खाली रहे. अगर उस उपयोगकर्ता को डिस्क में फिर से जगह मिलनी है, तो आपको प्राइम उम्मीदवार नहीं बनना है. पक्का करें कि आपने कैश मेमोरी में से ऐसे आइटम हटा दिए हों जिनकी अब आपको ज़रूरत नहीं है.

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.

इसके लिए सबसे सही: संसाधनों को बार-बार अपडेट करना, जहां सबसे नए वर्शन का होना ज़रूरी नहीं है. अवतार इस कैटगरी में आ सकते हैं.

अगर कैश मेमोरी में सेव किया गया कोई वर्शन उपलब्ध है, तो उसका इस्तेमाल करें. हालांकि, अगली बार अपडेट फ़ेच करें.

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 से काफ़ी मिलता-जुलता है.

पुश मैसेज पर

पुश मैसेज पर.
पुश मैसेज पर.

Push API की सुविधा सर्विस वर्कर पर मिलती है. इससे सर्विस वर्कर को ओएस की मैसेज सेवा से आने वाले मैसेज के जवाब में जागना पड़ता है. ऐसा तब भी होता है, जब उपयोगकर्ता ने आपकी साइट पर कोई टैब न खोला हो. सिर्फ़ सर्विस वर्कर को जगाया जाता है. जब आपने किसी पेज से ऐसा करने की अनुमति मांगी हो और उपयोगकर्ता को यह अनुरोध भेजा जा��गा.

इनके लिए सही है: सूचना से जुड़ा कॉन्टेंट, जैसे कि चैट मैसेज, ताज़ा खबर या ईमेल. इसके अलावा, ऐसा कॉन्टेंट भी तुरंत सिंक किया जा सकता है जो अक्सर बदलता नहीं है. जैसे, 'क्या-क्या करें' सूची में बदलाव या कैलेंडर में बदलाव.

आम तौर पर, आखिर में एक सूचना दिखती है. इस पर टैप करने पर, काम का पेज खुलता है या उस पर फ़ोकस होता है. हालांकि, ऐसा होने से पहले कैश मेमोरी को अपडेट करना बहुत ज़रूरी है. पुश मैसेज मिलने के समय, उपयोगकर्ता ज़रूर ऑनलाइन होता है. हालांकि, हो सकता है कि वह सूचना के साथ इंटरैक्ट करते समय ऑनलाइन न हो. इसलिए, यह ज़रूरी है कि इस कॉन्टेंट को ऑफ़लाइन उपलब्ध कराया जाए.

यह कोड, सूचना दिखाने से पहले कैश मेमोरी को अपडेट करता है:

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

हालांकि, आपको अक्सर इस मामले को खास तौर पर मैनेज करने की ज़रूरत नहीं पड़ती, कैश मेमोरी, नेटवर्क पर वापस जाना इसे कवर करता है.

सिर्फ़ नेटवर्क

सिर्फ़ नेटवर्क.
सिर्फ़ नेटवर्क.

इसके लिए सबसे सही: ऐसी चीज़ें जो ऑफ़लाइन मिलती-जुलती नहीं हैं, जैसे कि Analytics पिंग, गैर-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;
      });
    }),
  );
});

trained-to-thrill में, मैंने इस समस्या को हल करने के लिए, फ़ेच के बजाय XHR का इस्तेमाल किया. साथ ही, Accept हेडर का गलत इस्तेमाल करके, सर्विस वर्कर को यह बताया कि नतीजा कहां से पाना है (पेज कोड, सर्विस वर्कर कोड).

सामान्य फ़ॉलबैक

सामान्य फ़ॉलबैक.
सामान्य फ़ॉलबैक.

अगर कैश मेमोरी और/या नेटवर्क से कोई कॉन्टेंट नहीं मिलता है, तो कोई सामान्य फ़ॉलबैक दिया जा सकता है.

इनके लिए सही है: अवतार, पोस्ट करने के अनुरोधों के अस्वीकार होने, और "ऑफ़लाइन होने पर उपलब्ध नहीं" पेज जैसी दूसरी इमेज.

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

एक साथ रखना

इनमें से किसी भी तरीके का इस्तेमाल किया जा सकता है. असल में, अनुरोध के यूआरएल के आधार पर, आपको इनमें से कई का इस्तेमाल करना पड़ सकता है. उदाहरण के लिए, थ्रिल देने के लिए ट्रेन किया गया एआई इनका इस्तेमाल करता है:

अनुरोध देखें और तय करें कि क्या करना है:

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

…आपको यह समझ आ गया होगा.

क्रेडिट

…इन खूबसूरत आइकॉन के लिए:

साथ ही, जेफ़ पॉस्निक को धन्यवाद, जिन्होंने "पब्लिश करें" पर क्लिक करने से पहले, कई गड़बड़ियों का पता लगाया.

इसके बारे में और पढ़ें