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

news/2024/7/6 3:12:41 标签: 前端, json, javascript
[[译]前端离线指南(上)]( 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中使用了:

  • 在安装时缓存,适用于静态UI
  • 在网络响应时缓存,适用于网络图片和数据
  • 从缓存中获取,若失败回退到网络,适用于大部分的请求
  • 从缓存中获取,然后请求网络,适用于网络搜索结果

只需要根据请求,就能决定要做什么:

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 在我点击“发布”按钮之前,为我找到多处明显错误。

扩展阅读

  • Intro to ServiceWorkers
  • Is ServiceWorker ready? - 跟踪主流浏览器对ServiceWorker的实现状态
  • JavaScript promises, there and back again - Promise指南

http://www.niftyadmin.cn/n/928956.html

相关文章

SQL手工注入原理(含环境搭建) ─=≡Σ(((つ•̀ω•́)つ 知己知彼百战百胜 web安全

文章目录SQL测试环境环境搭建下载与安装环境开启与使用环境sqli-labs环境部署dvwa环境部署SQL手动注入SQL注入原理SQL注入分类SQL注入手段寻找注点利用注点get注入get显错注入使用order by判断字段数使用union select联合查询get盲注基于时间的盲注基于布尔的盲注POST注入POST显…

JavaEE5种常见的设计模式

1、工厂模式&#xff1a;比如你写了个应用&#xff0c;里面用到了数据库的封装&#xff0c;你的应用可以今后需要在不同的数据库环境下运行&#xff0c;可能是oracle,db2,sql server等&#xff0c; 那么连接数据库的代码是不一样的&#xff0c;你用传统的方法&#xff0c;就不得…

《架构设计之[CAP定理]》读后感

现在有许多互联网项目都是采用分布式结构进行部署。而cap定理是分布式系统中最近出的原则。所以对于哦我们来说&#xff0c;学习cap非常重要。CAP定理又称为布鲁尔定理。CAP定理是指对于一个分布式系统&#xff0c;不能同时满足一致性&#xff0c;可用性&#xff0c;分区容错性…

React的异步组件

最近在学习react&#xff0c;用到了异步组件&#xff0c;实现按需加载chunk.js&#xff0c;减轻首页压力。话不多说&#xff0c;直接上代码&#xff1a;import React, { Component } from "react"; export default function asyncComponent(importComponent) { cla…

程序员协同办公利器 Git,Git从搭建到使用,超完善教程 (包含vscode使用Git与pycharm使用Git方法) ╰( ´・ω・)つ──☆✿✿✿ 项目协同

文章目录Git简介Git的搭建自行搭建Git(创建Git私服)安装Git配置Git用户创建仓库免密处理生成公钥第三方Git平台GitHub简介使用GitHub创建并同步项目GitHub免密处理Git的使用下载与安装git同步仓库最基础的Git方法初次使用gitGit基础配置新仓库非空仓库常用的Git方法Git分支解决…

curl 模拟表单post文件

网上查询出来的几乎都是错误的&#xff0c;正确的应该是&#xff1a; $data array(pic>new CURLFile($path)// 如果无效可以这样 // pic>curl_file_create($path) ); 更多参考&#xff1a;https://www.jianshu.com/p/63b32ceea742 https://blog.csdn.net/a735311619/art…

xss攻击-面向前端的安全攻击 ─=≡Σ(((つ•̀ω•́)つ 知己知彼百战百胜 web安全

文章目录XSS简介测试环境XSS工具步骤与分类XSS用到的一些HTML和JSHTML表单文本框介绍探测xss反射型XSS常用的反射型XSS攻击方法闭合标签使用下拉菜单使用隐藏输入框解除输入框限制使用HTML事件启动攻击JavaScript伪代码XSS攻击防御手段绕过双写绕过编码绕过利用IE特性存储型XSS…

死磕 java集合之LinkedHashSet源码分析

问题 &#xff08;1&#xff09;LinkedHashSet的底层使用什么存储元素&#xff1f; &#xff08;2&#xff09;LinkedHashSet与HashSet有什么不同&#xff1f; &#xff08;3&#xff09;LinkedHashSet是有序的吗&#xff1f; &#xff08;4&#xff09;LinkedHashSet支持按元素…