[译]前端离线指南(下)

[[译]前端离线指南(上)](
https://juejin.im/post/5c0788…

原文链接:The offline cookbook 作者:Jake Archibald

缓存耐久化

为您的站点供应一定量的可用空间来实行其所需的操纵。该可用空间可在站点中一切存储之间同享:LocalStorage、IndexedDB、Filesystem,固然也包含Caches。

您能猎取到的空间容量是不一定的,同时由于装备和存储前提的差别也会有所差别。您能够经由过程下面的代码来检察您已取得的空间容量:

navigator.storageQuota.queryInfo("temporary").then((info) => {
  console.log(info.quota);
  // Result: <quota in bytes>
  console.log(info.usage);
  // Result: <used data in bytes>
});

然则,与一切浏览器存储一样,假如装备面对存储压力,浏览器就会随时舍弃这些存储内容。但遗憾的是,浏览器没法辨别您收藏的影戏,和您没啥兴致的游戏之间有啥区分。

为处置惩罚此题目,提议运用 requestPersistent API:

// 在页面中运转
navigator.storage.requestPersistent().then((granted) => {
  if (granted) {
    // 啊哈,数据保存在这里呢
  }
});

固然,用户必须要授与权限。让用户介入进这个流程是很有必要的,由于我们能够预期用户会掌握删除。假如用户手中的装备面对存储压力,而且消灭不重要的数据还没能处置惩罚题目,那末用户就须要依据本身的推断来决议删除哪些项目以及保存哪些项目。

为了完成此目标,须要操纵系统将“耐久化”源等同于其存储运用空间细分中的本机运用,而不是作为单个项目报告给浏览器。

缓存提议-响应要求

不管您盘算缓存若干内容,除非您通知ServiceWorker应当在什么时刻以及怎样去缓存内容,ServiceWorker不会去主动运用缓存。下面是几种用于处置惩罚要求的战略。

仅缓存(cache only)

《[译]前端离线指南(下)》

适用于: 您认为在站点的“该版本”中属于静态内容的任何资本。您应当在install事宜中就缓存这些资本,以便您能够在处置惩罚要求的时刻依托它们。

self.addEventListener('fetch', (event) => {
  // 假如某个婚配到的资本在缓存中找不到,
  // 则响应效果看起来就会像一个衔接毛病。
  event.respondWith(caches.match(event.request));
});

…只管您平常不须要经由过程特别的体式格局来处置惩罚这类状况,但“缓存,回退到收集”涵盖了这类战略。

仅收集(network only)

《[译]前端离线指南(下)》
适用于: 没有响应的离线资本的对象,比方analytics pings,非GET要求。

self.addEventListener('fetch', (event) => {
  event.respondWith(fetch(event.request));
  // 或许简朴地不再挪用event.respondWith,如许就会
  // 致使默许的浏览器行动
});

…只管您平常不须要经由过程特别的体式格局来处置惩罚这类状况,但“缓存,回退到收集”涵盖了这类战略。

缓存优先,若无缓存则回退到收集(Cache, falling back to network)

《[译]前端离线指南(下)》
适用于: 假如您以缓存优先的体式格局构建,那末这类战略就是您处置惩罚大多数要求时所用的战略 依据传入要求而定,其他战略会有破例。

self.addEventListener('fetch', (event) => {
  event.respondWith(async function() {
    const response = await caches.match(event.request);
    return response || fetch(event.request);
  }());
});

个中,针对已缓存的资本供应“Cache only”的行动,针对未缓存的资本(包含一切非GET要求,由于它们基础没法被缓存)供应“Network only”的行动。

缓存与收集合作

《[译]前端离线指南(下)》
适用于: 存储在读取速率慢的硬盘中的小型资本。

在老旧硬盘、病毒扫描递次、和较快网速这几种要素都存在的状况下,从收集中猎取资本能够比从硬盘中猎取的速率更快。不过,经由过程收集猎取已经在用户装备中保存过的内容,是一种糟蹋流量的行动,所以请切记这一点。

// Promise.race 对我们来讲并不太好,由于若当个中一个promise在
// fulfilling之前reject了,那末全部Promise.race就会返回reject。
// 我们来写一个更好的race函数:
function promiseAny(promises) {
  return new Promise((resolve, reject) => {
    // 确保promises代表一切的promise对象。
    promises = promises.map(p => Promise.resolve(p));
    // 只需当个中一个promise对象挪用了resolve,就让此promise对象变成resolve的
    promises.forEach(p => p.then(resolve));
    // 假如传入的一切promise都reject了,就让此promise对象变成resject的
    promises.reduce((a, b) => a.catch(() => b))
      .catch(() => reject(Error("All failed")));
  });
};

self.addEventListener('fetch', (event) => {
  event.respondWith(
    promiseAny([
      caches.match(event.request),
      fetch(event.request)
    ])
  );
});

经由过程收集猎取失利回退到缓存(Network falling back to cache)

《[译]前端离线指南(下)》

适用于: 对频仍更新的资本举行疾速修复。比方:文章、头像、交际媒体时间轴、游戏排行榜等。

这就意味着您能够为在线用户供应最新内容,然则离线用户猎取到的是较老的缓存版本。假如收集要求胜利,您能够须要更新缓存。

不过,这类要领存在缺点。假如用户的收集断断续续,或许网速超慢,则用户能够会在从本身装备中猎取更好的、可接受的内容之前,花很长一段时间去守候收集要求失利。如许的用户体验是异常蹩脚的。请检察下一个更好的处置惩罚计划:“缓存然后接见收集”。

self.addEventListener('fetch', (event) => {
  event.respondWith(async function() {
    try {
      return await fetch(event.request);
    } catch (err) {
      return caches.match(event.request);
    }
  }());
});

缓存然后接见收集

《[译]前端离线指南(下)》
适用于: 更新频仍的内容。比方:文章、交际媒体时间轴、游戏排行榜等。

这类战略须要页面提议两个要求,一个是要求缓存,一个是要求收集。起首展现缓存数据,然后当收集数据抵达的时刻,更新页面。

有时刻,您能够在猎取到新的数据的时刻,只替代当前数据(比方:游戏排行榜),然则具有较大的内容时将致使数据中缀。基本上讲,不要在用户能够正在浏览或正在操纵的内容倏忽“消逝”。

Twitter在旧内容上增加新内容,并调解转动的位置,以便让用户感知不到。这是能够的,由于 Twitter 通常会坚持使内容最具线性特征的递次。 我为 trained-to-thrill 复制了此情势,以尽快猎取屏幕上的内容,但当它出现时仍会显现最新内容。
页面中的代码

async function update() {
  // 尽量地提议收集要求
  const networkPromise = fetch('/data.json');

  startSpinner();

  const cachedResponse = await caches.match('/data.json');
  if (cachedResponse) await displayUpdate(cachedResponse);

  try {
    const networkResponse = await networkPromise;
    const cache = await caches.open('mysite-dynamic');
    cache.put('/data.json', networkResponse.clone());
    await displayUpdate(networkResponse);
  } catch (err) {
   
  }

  stopSpinner();

  const networkResponse = await networkPromise;

}

async function displayUpdate(response) {
  const data = await response.json();
  updatePage(data);
}

通例回退

《[译]前端离线指南(下)》

假如您未能从收集和缓存中供应某些资本,您能够须要一个通例回退战略。

适用于: 次要的图片,比方头像,失利的POST要求,“离线时不可用”的页面。

self.addEventListener('fetch', (event) => {
  event.respondWith(async function() {
    // 尝试从缓存中婚配
    const cachedResponse = await caches.match(event.request);
    if (cachedResponse) return cachedResponse;

    try {
      // 回退到收集
      return await fetch(event.request);
    } catch (err) {
      // 假如都失利了,启用通例回退:
      return caches.match('/offline.html');
      // 不过,事实上您须要依据URL和Headers,预备多个差别回退计划
      // 比方:头像的兜底图
    }
  }());
});

您回退到的项目多是一个“装置依靠项”(见《前端离线指南(上)》中的“装置时——以依靠的情势”小节)。

ServiceWorker-side templating

《[译]前端离线指南(下)》
适用于: 没法缓存其服务器响应的页面。

在服务器上衬着页面可进步速率,但这意味着会包含在缓存中没有意义的状况数据,比方,“Logged in as…”。假如您的页面由 ServiceWorker 掌握,您能够会转而挑选要求 JSON 数据和一个模板,并举行衬着。

importScripts('templating-engine.js');

self.addEventListener('fetch', (event) => {
  const requestURL = new URL(event.request);

  event.responseWith(async function() {
    const [template, data] = await Promise.all([
      caches.match('/article-template.html').then(r => r.text()),
      caches.match(requestURL.path + '.json').then(r => r.json()),
    ]);

    return new Response(renderTemplate(template, data), {
      headers: {'Content-Type': 'text/html'}
    })
  }());
});

总结

您没必要只挑选个中的一种要领,您能够依据要求URL挑选运用多种要领。比方,在trained-to-thrill中运用了:

只须要依据要求,就可以决议要做什么:

self.addEventListener('fetch', (event) => {
  // Parse the URL:
  const 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 (requestURL.pathname.endsWith('.webp')) {
      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(async function() {
    const cachedResponse = await caches.match(event.request);
    return cachedResponse || fetch(event.request);
  }());
});

道谢

谢谢以下诸君为本文供应那些可爱的图标:

  • Code,作者:buzzyrobot
  • Calendar,作者:Scott Lewis
  • Network,作者:Ben Rizzo
  • SD,作者:Thomas Le Bas
  • CPU,作者:iconsmind.com
  • Trash,作者:trasnik
  • Notification,作者:@daosme
  • Layout,作者:Mister Pixel
  • Cloud,作者:P.J. Onori

同时谢谢 Jeff Posnick 在我点击“宣布”按钮之前,为我找到多处显著毛病。

扩大浏览

    原文作者:司楠
    原文地址: https://segmentfault.com/a/1190000017302009
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞