ऑफ़लाइन ऐप्लिकेशन बनाने की सामान्य तकनीकें

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

इनमें से कुछ पैटर्न का डेमो देखने के लिए, Trained-to-thrill देखें.

संसाधन कब सेव किए जाते हैं

Browser Support

  • Chrome: 40.
  • Edge: 17.
  • Firefox: 44.
  • Safari: 11.1.

Source

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

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

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

Service Worker API आपको install इवेंट देता है. इसका इस्तेमाल, इवेंट से पहले की जाने वाली ज़रूरी कार्रवाइयों को पूरा करने के लिए किया जा सकता है. 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() कॉल अस्वीकार कर देता है.

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

लेवल 11 से 20 डाउनलोड करते समय, सर्विस वर्कर बंद हो सकता है. ऐसा इसलिए, क्योंकि वह इवेंट मैनेज करने का काम पूरा कर चुका है. इसका मतलब है कि उन्हें कैश मेमोरी में सेव नहीं किया जाएगा. Web Periodic Background Synchronization API इस तरह के मामलों को हैंडल कर सकता है. साथ ही, यह फ़िल्मों जैसे बड़े डाउनलोड को भी हैंडल कर सकता है.

Browser Support

  • Chrome: 40.
  • Edge: 17.
  • Firefox: 44.
  • Safari: 11.1.

Source

चालू करने पर

चालू होने पर.

इनके लिए सबसे सही है: डेटा को व्यवस्थित करना और माइग्रेट करना.

जब नया सर्विस वर्कर इंस्टॉल हो जाता है और पिछले वर्शन का इस्तेमाल नहीं किया जा रहा होता है, तब नया सर्विस वर्कर चालू हो जाता है. इसके बाद, आपको 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);
      });
  });
});

Cache API, पेजों और सर्विस वर्कर के लिए उपलब्ध है. इसका मतलब है कि पेज से सीधे कैश मेमोरी में डेटा जोड़ा जा सकता है.

Browser Support

  • Chrome: 40.
  • Edge: 16.
  • Firefox: 41.
  • Safari: 11.1.

Source

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

नेटवर्क के जवाब पर.

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

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

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

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.

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

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

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

On background-sync

On background-sync.

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

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

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 का इस्तेमाल करें.

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

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

सुझाव दिखाए जा रहे हैं

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

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

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

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

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 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 rejects when a promise rejects before fulfilling.
// To make a 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 का इस्तेमाल किया. साथ ही, 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 आउटबॉक्स में सेव कर सकता है. साथ ही, पेज को यह सूचना दे सकता है कि ईमेल नहीं भेजा जा सका, लेकिन डेटा सेव हो गया है.

सर्विस वर्कर-साइड टेंप्लेटिंग

Service worker-side templating.

इनके लिए सबसे सही है: ऐसे पेज जिनके सर्वर रिस्पॉन्स को कैश मेमोरी में सेव नहीं किया जा सकता.

सर्वर पर पेजों को रेंडर करना ज़्यादा तेज़ होता है. हालांकि, इसका मतलब यह हो सकता है कि इसमें ऐसा स्टेट डेटा शामिल हो जो कैश में काम का न हो. जैसे, साइन-इन की स्थिति. अगर आपके पेज को सर्विस वर्कर कंट्रोल करता है, तो आपके पास टेंप्लेट के साथ-साथ 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',
        },
      });
    }),
  );
});

एक साथ लाएं

इनमें से सिर्फ़ एक तरीका इस्तेमाल करना ज़रूरी नहीं है. दरअसल, अनुरोध किए गए यूआरएल के आधार पर, इनमें से कई का इस्तेमाल किया जा सकता है. उदाहरण के लिए, 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);
    }),
  );
});

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

क्रेडिट

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

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