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

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

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

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

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

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

इंस्टॉल करने पर - निर्भरता के तौर पर.
इंस्टॉल करने पर - निर्भरता के तौर पर.

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

इनके लिए सबसे सही: सीएसएस, इमेज, फ़ॉन्ट, 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() कॉल अस्वीकार हो जाता है.

ट्रेन्ड-टू-थ्रिल पर मैं इसका इस्तेमाल स्टैटिक ऐसेट को कैश मेमोरी में करने के लिए करता/करती हूं.

इंस्टॉल करने पर—एक निर्भरता के रूप में नहीं

इंस्टॉल करने पर - निर्भरता के तौर पर नहीं.
इंस्टॉल करने पर - निर्भरता के तौर पर नहीं.

यह ऊपर जैसा है. हालांकि, इंस्टॉल पूरा होने में देरी नहीं होगी. साथ ही, कैश मेमोरी में डेटा सेव करने में गड़बड़ी होने पर भी, इंस्टॉल करने की प्रोसेस पूरी नहीं होगी.

इनके लिए सबसे बेहतर: ऐसे बड़े संसाधन जिनकी तुरंत ज़रूरत नहीं होती, जैसे कि गेम के बाद के लेवल के लिए ऐसे एसेट.

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

ऊपर दिया गया उदाहरण, 11 से 20 लेवल के लिए cache.addAll के प्रॉमिस को event.waitUntil पर वापस पूरा नहीं करता. इसलिए, ऐसा न होने पर भी गेम ऑफ़लाइन उपलब्ध रहेगा. बिलकुल, आपको उन लेवल की संभावित गैर-मौजूदगी को पूरा करना होगा. साथ ही, अगर वे मौजूद नहीं हैं, तो उन्हें कैश मेमोरी में सेव करने की फिर से कोशिश करनी होगी.

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

उपयोगकर्ता को "बाद में पढ़ें" या "ऑफ़लाइन देखने के लिए सेव करें" बटन दें. जब इस पर क्लिक हो, तो नेटवर्क से अपनी ज़रूरत की चीज़ें फ़ेच करें और उसे कैश मेमोरी में पॉप-अप करें.

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() का इस्तेमाल करके ऐसी अतिरिक्त कॉपी बनाता है जिन्हें अलग से पढ़ा जा सकता है.

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

पुरानी-पुष्टि के दौरान

पुरानी-पुष्टि के दौरान.
दोबारा पुष्टि करते समय पुरानी जानकारी.

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

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

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

पुश मैसेज पर

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

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

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

सामान्य नतीजा एक ऐसी सूचना होती है जिस पर टैप करने से वह पेज खुलता है या उस पर फ़ोकस होता है जो काम का है. हालांकि, ऐसा करने से पहले कैश मेमोरी को अपडेट करना 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.
});

बेशक, उपयोगकर्ता को अनुमति देनी होगी. इसके लिए, अनुमतियों के एपीआई का इस्तेमाल करें.

उपयोगकर्ता को इस फ़्लो का हिस्सा बनाना ज़रूरी है, क्योंकि अब हम उम्मीद कर सकते हैं कि अब डेटा मिटाने की प्रक्रिया का कंट्रोल उनके पास होगा. अगर उनके डिवाइस पर स्टोरेज कम है और ग़ैर-ज़रूरी डेटा को हटाने से भी यह समस्या हल नहीं होती है, तो उपयोगकर्ता यह तय करता है कि उसे कौनसे आइटम रखने हैं और हटाने हैं.

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

सुझाव देना—अनुरोधों का जवाब देना

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

सिर्फ़ कैश मेमोरी

सिर्फ़ कैश मेमोरी.
सिर्फ़ कैश मेमोरी.

इसके लिए सबसे सही: ऐसी कोई भी चीज़ जिसके लिए आपको अपनी साइट के किसी खास "वर्शन" के लिए स्टैटिक वैल्यू चाहिए. आपको इंस्टॉल इवेंट में इन्हें कैश मेमोरी में सेव करना चाहिए, ताकि आप उनके वहां मौजूद रहने पर निर्भर रहें.

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

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

कैश और नेटवर्क रेस

कैश और नेटवर्क रेस.
कैश मेमोरी और नेटवर्क रेस.

आपके लिए सबसे सही: छोटी ऐसेट, जिनके लिए डिस्क का धीमा ऐक्सेस रखने वाले डिवाइसों की परफ़ॉर्मेंस के हिसाब से मेहनत की जाती है.

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

// 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 का इस्तेमाल करके इस समस्या को ठीक किया और सर्विस वर्कर को यह बताने के लिए स्वीकार करें हेडर का गलत इस्तेमाल किया कि नतीजा कहां से मिलेगा (पेज कोड, सर्विस वर्कर कोड).

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

सामान्य फ़ॉलबैक.
जेनरिक फ़ॉलबैक.

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

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

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

...आपको तस्वीर समझ आ जाएगी.

क्रेडिट देखें

...प्यारे आइकॉन के लिए:

"पब्लिश करें" दबाने से पहले, शोर-शराबे वाली गड़बड़ियां ठीक करने के लिए जेफ़ पॉसनिक का शुक्रिया.

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