تکنیک‌های رایج برای ساخت برنامه‌های آفلاین

جیک آرچیبالد
Jake Archibald

با Service Workerها ، ما به توسعه‌دهندگان راهی برای حل مشکل اتصال به شبکه ارائه دادیم. شما کنترل ذخیره‌سازی و نحوه‌ی مدیریت درخواست‌ها را در اختیار دارید. این بدان معناست که می‌توانید الگوهای خودتان را ایجاد کنید. نگاهی به چند الگوی ممکن به صورت جداگانه بیندازید، اما در عمل، بسته به URL و متن، احتمالاً از آن‌ها به صورت پشت سر هم استفاده خواهید کرد.

برای نمایش نمایشی برخی از این الگوها، به Trained-to-thrill مراجعه کنید.

چه زمانی منابع را ذخیره کنیم

Browser Support

  • کروم: ۴۰.
  • لبه: ۱۷.
  • فایرفاکس: ۴۴.
  • سافاری: ۱۱.۱.

Source

سرویس ورکرها به شما اجازه می‌دهند درخواست‌ها را مستقل از ذخیره‌سازی (caching) مدیریت کنید، بنابراین من آنها را جداگانه نشان خواهم داد. اول از همه، تعیین کنید که چه زمانی باید از حافظه پنهان (cache) استفاده کنید.

در نصب، به عنوان یک وابستگی

در هنگام نصب، به عنوان یک وابستگی.

API سرویس ورکر یک رویداد install به شما می‌دهد. می‌توانید از این برای آماده‌سازی موارد استفاده کنید، مواردی که باید قبل از مدیریت سایر رویدادها آماده باشند. در طول install ، نسخه‌های قبلی سرویس ورکر شما به اجرا و ارائه صفحات ادامه می‌دهند. هر کاری که در این زمان انجام دهید نباید سرویس ورکر موجود را مختل کند.

ایده‌آل برای : CSS، تصاویر، فونت‌ها، 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 یک promise برای تعریف مدت زمان و موفقیت نصب می‌گیرد. اگر promise رد شود، نصب به عنوان یک شکست در نظر گرفته می‌شود و این Service Worker رها می‌شود (اگر نسخه قدیمی‌تری در حال اجرا باشد، دست نخورده باقی می‌ماند). caches.open() و cache.addAll() promiseها را برمی‌گردانند. اگر هر یک از منابع دریافت نشوند، فراخوانی 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
        ();
    }),
  );
});

این مثال، promise مربوط به سطوح ۱۱ تا ۲۰ از cache.addAll را به event.waitUntil ارسال نمی‌کند، بنابراین حتی اگر با شکست مواجه شود، بازی همچنان به صورت آفلاین در دسترس خواهد بود. البته، شما باید احتمال عدم وجود این سطوح را در نظر بگیرید و در صورت عدم وجود، دوباره برای ذخیره آنها تلاش کنید.

ممکن است سرویس ورکر در حین دانلود سطوح ۱۱ تا ۲۰ از بین برود، زیرا مدیریت رویدادها را به پایان رسانده است، به این معنی که آنها ذخیره نخواهند شد. API همگام‌سازی پس‌زمینه دوره‌ای وب می‌تواند مواردی مانند این و دانلودهای بزرگتر مانند فیلم‌ها را مدیریت کند.

Browser Support

  • کروم: ۴۰.
  • لبه: ۱۷.
  • فایرفاکس: ۴۴.
  • سافاری: ۱۱.۱.

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 در صف قرار می‌گیرند، بنابراین یک فعال‌سازی طولانی می‌تواند بارگذاری صفحه را متوقف کند. فعال‌سازی خود را تا حد امکان ساده نگه دارید و فقط از آن برای کارهایی استفاده کنید که در زمان فعال بودن نسخه قبلی نمی‌توانستید انجام دهید.

در حالت آموزش دیده برای هیجان، از این برای حذف حافظه‌های پنهان قدیمی استفاده می‌کنم.

در مورد تعامل کاربر

در مورد تعامل کاربر.

ایده‌آل برای : زمانی که کل سایت را نمی‌توان آفلاین کرد، و شما تصمیم گرفته‌اید که به کاربر اجازه دهید محتوایی را که می‌خواهد به صورت آفلاین در دسترس باشد، انتخاب کند. مثلاً یک ویدیو در چیزی مانند یوتیوب، یک مقاله در ویکی‌پدیا، یک گالری خاص در فلیکر.

به کاربر دکمه‌ی «بعداً بخوانید» یا «ذخیره برای حالت آفلاین» بدهید. وقتی روی آن کلیک شد، آنچه را که نیاز دارید از شبکه دریافت کرده و در حافظه‌ی پنهان قرار دهید.

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

  • کروم: ۴۰.
  • لبه: ۱۶.
  • فایرفاکس: ۴۱.
  • سافاری: ۱۱.۱.

Source

پاسخ شبکه

در مورد پاسخ شبکه.

ایده‌آل برای : به‌روزرسانی مکرر منابعی مانند صندوق ورودی کاربر یا محتوای مقاله. همچنین برای محتوای غیرضروری مانند آواتارها مفید است، اما دقت لازم است.

اگر درخواستی با هیچ چیزی در حافظه پنهان مطابقت ندارد، آن را از شبکه دریافت کنید، به صفحه ارسال کنید و همزمان آن را به حافظه پنهان اضافه کنید.

اگر این کار را برای طیف وسیعی از 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() برای ایجاد کپی‌های اضافی که می‌توانند جداگانه خوانده شوند، استفاده می‌کند.

در حالت آموزش‌دیده برای هیجان‌زده شدن، از این برای ذخیره تصاویر فلیکر استفاده می‌کنم.

اعتبارسنجی مجدد در حین بی‌اعتبارسازی

اعتبارسنجی مجدد در حین کهنه شدن.

ایده‌آل برای : به‌روزرسانی مکرر منابع که در آن‌ها داشتن آخرین نسخه ضروری نیست. آواتارها می‌توانند در این دسته قرار بگیرند.

اگر نسخه ذخیره‌شده‌ای موجود است، از آن استفاده کنید، اما برای دفعه بعد به‌روزرسانی را دریافت کنید.

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 است.

پیام فوری

پیام فوری.

API Push یکی دیگر از ویژگی‌های ساخته شده بر روی سرویس ورکر است. این به سرویس ورکر اجازه می‌دهد تا در پاسخ به پیامی از سرویس پیام‌رسانی سیستم عامل، بیدار شود. این اتفاق حتی زمانی که کاربر هیچ تبی در سایت شما باز نکرده باشد، رخ می‌دهد. فقط سرویس ورکر بیدار می‌شود. شما از یک صفحه درخواست اجازه برای انجام این کار را می‌کنید و از کاربر خواسته می‌شود.

ایده‌آل برای : محتوای مربوط به اعلان، مانند یک پیام چت، یک خبر فوری یا یک ایمیل. همچنین محتوایی که به ندرت تغییر می‌کند و از همگام‌سازی فوری سود می‌برد، مانند به‌روزرسانی لیست کارها یا تغییر تقویم.

نتیجه نهایی مشترک، اعلانی است که با لمس آن، صفحه مربوطه باز شده و روی آن تمرکز می‌شود و به‌روزرسانی حافظه‌های پنهان (cache) از قبل بسیار مهم است. کاربر در زمان دریافت پیام آنلاین است، اما ممکن است وقتی که در نهایت با اعلان تعامل می‌کند، آنلاین نباشد، بنابراین بسیار مهم است که این محتوا به صورت آفلاین در دسترس باشد.

این کد قبل از نمایش اعلان، حافظه‌های پنهان (cache) را به‌روزرسانی می‌کند:

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

ماندگاری حافظه پنهان

به origin شما مقدار مشخصی فضای خالی داده می‌شود تا هر کاری که می‌خواهد با آن انجام دهد. این فضای خالی بین تمام فضای ذخیره‌سازی origin به اشتراک گذاشته شده است: (local) Storage ، IndexedDB ، File System Access و البته Caches .

مقدار مشخصی برای آن تعیین نشده است. بسته به دستگاه و شرایط نگهداری متفاوت است. می‌توانید با استفاده از موارد زیر میزان آن را متوجه شوید:

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

البته، کاربر باید مجوز بدهد. برای این کار، از 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 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 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)]));
});

شبکه‌ای که به حافظه پنهان برمی‌گردد

شبکه به حافظه پنهان برمی‌گردد.

ایده‌آل برای : یک راه‌حل سریع برای منابعی که مرتباً به‌روزرسانی می‌شوند، خارج از «نسخه» سایت. مانند مقالات، آواتارها، جدول زمانی رسانه‌های اجتماعی و جدول امتیازات بازی‌ها.

این یعنی شما به کاربران آنلاین، جدیدترین محتوا را ارائه می‌دهید، اما کاربران آفلاین، نسخه قدیمی‌تر ذخیره‌شده را دریافت می‌کنند. اگر درخواست شبکه موفقیت‌آمیز باشد، به احتمال زیاد می‌خواهید ورودی ذخیره‌شده را به‌روزرسانی کنید .

با این حال، این روش دارای نقص‌هایی است. اگر کاربر اتصال متناوب یا کندی داشته باشد، باید منتظر بماند تا شبکه از کار بیفتد تا محتوای کاملاً قابل قبول را از قبل روی دستگاه خود دریافت کند. این می‌تواند زمان بسیار زیادی طول بکشد و یک تجربه کاربری ناامیدکننده است. برای راه‌حل بهتر، به الگوی بعدی، یعنی ابتدا حافظه پنهان (Cache) و سپس شبکه (Network) ، مراجعه کنید.

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

ابتدا حافظه پنهان (کش) و سپس شبکه

ابتدا حافظه پنهان (cache) و سپس شبکه (network).

ایده‌آل برای : محتوایی که مرتباً به‌روزرسانی می‌شود. مانند مقالات، جدول زمانی رسانه‌های اجتماعی و بازی‌ها. جدول امتیازات.

این امر مستلزم آن است که صفحه دو درخواست ارسال کند، یکی به حافظه پنهان و دیگری به شبکه. ایده این است که ابتدا داده‌های ذخیره شده در حافظه پنهان نمایش داده شوند، سپس صفحه به محض رسیدن داده‌های شبکه به‌روزرسانی شود.

گاهی اوقات می‌توانید داده‌های فعلی را هنگام رسیدن داده‌های جدید جایگزین کنید (مانند جدول امتیازات بازی)، اما این کار می‌تواند با محتوای بزرگ‌تر اختلال ایجاد کند. اساساً، چیزی را که کاربر ممکن است در حال خواندن یا تعامل با آن باشد، «ناپدید» نکنید.

توییتر محتوای جدید را بالای محتوای قدیمی اضافه می‌کند و موقعیت اسکرول را طوری تنظیم می‌کند که کاربر بدون وقفه در حال اسکرول کردن باشد. این امر به این دلیل امکان‌پذیر است که توییتر ترتیب خطی محتوا را حفظ می‌کند. من این الگو را برای الگوریتم «آموزش-به-هیجان» کپی کردم تا محتوا را در سریع‌ترین زمان ممکن روی صفحه نمایش دهم، در حالی که محتوای به‌روز را به محض رسیدن نمایش می‌دهم.

کد موجود در صفحه :

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

کد موجود در سرویس ورکر :

شما همیشه باید به شبکه بروید و همزمان با آن، حافظه پنهان (cache) را به‌روزرسانی کنید.

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 بازگردد و با گفتن اینکه ارسال ناموفق بود اما داده‌ها با موفقیت حفظ شدند، پاسخ دهد.

قالب‌بندی سمت کارگر سرویس

قالب‌بندی سمت کارگر سرویس.

ایده‌آل برای : صفحاتی که نمی‌توانند پاسخ سرور خود را ذخیره کنند.

رندر کردن صفحات روی سرور سریع‌تر است، اما این می‌تواند به معنای گنجاندن داده‌های وضعیتی باشد که ممکن است در حافظه پنهان منطقی نباشند، مانند وضعیت ورود به سیستم. اگر صفحه شما توسط یک سرویس ورکر کنترل می‌شود، می‌توانید داده‌های 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 درخواستی، احتمالاً از بسیاری از آنها استفاده خواهید کرد. به عنوان مثال، روش train-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);
    }),
  );
});

مطالعه بیشتر

اعتبارات

برای آیکون‌های زیبا:

و از جف پوزنیک هم ممنونم که قبل از اینکه دکمه‌ی «انتشار» را بزنم، خطاهای فاحش زیادی را پیدا کرد.