朋友们,美好的一天!
我注意到了Jake Archibald出色的文章“离线食谱”的翻译,该文章专门介绍ServiceWorker API和Cache API的各种用例。
假定您熟悉这些技术的基础,因为将有大量的代码和少量的单词。
如果您不熟悉,请从MDN开始,然后再回来。这是另一篇关于服务人员的专门针对视觉的好文章。
没有更多的前言。
何时节省资源?
worker允许您独立于缓存处理请求,因此我们将单独考虑它们。
第一个问题是何时应该缓存资源?
作为依赖项安装时
安装程序事件是工作程序运行时发生的事件之一。此事件可用于准备处理其他事件。安装新工作程序后,旧工作程序将继续为该页面提供服务,因此处理install事件不应破坏它。通常
适用于缓存样式,图像,脚本,模板...,通常用于页面上使用的任何静态文件。
我们正在谈论那些文件,如果没有这些文件,应用程序将无法像本机应用程序的初始下载中包含的文件那样工作。
self.addEventListener('install', event => {
event.waitUntil(
caches.open('mysite-static-v3')
.then(cache => cache.addAll([
'/css/whatever-v3.css',
'/css/imgs/sprites-v6.png',
'/css/fonts/whatever-v8.woff',
'/js/all-min-v4.js'
// ..
]))
)
})
event.waitUntil接受承诺来确定安装的持续时间和结果。如果承诺被拒绝,则不会安装该工作程序。caches.open和cache.addAll返回承诺。如果其中一种资源不可用,则
对cache.addAll的调用将被拒绝。
安装时不作为依赖项
这与前面的示例相似,但是在这种情况下,我们不等待安装完成,因此不会取消安装。
适用于当前不需要的大型资源,例如游戏后期版本的资源。
self.addEventListener('install', event => {
event.waitUntil(
caches.open('mygame-core-v1')
.then(cache => {
cache.addAll(
// 11-20
)
return cache.addAll(
// 1-10
)
})
)
})
我们不会将11.-20级的cache.addAll承诺传递给event.waitUntil,因此,如果拒绝,游戏仍将离线运行。当然,您应该注意缓存第一级的可能问题,例如,如果出现故障,请再次尝试缓存。
可以在处理事件后停止工作程序,然后将其缓存到11-20级。这意味着这些级别将不会保存。将来,计划为工作人员添加后台加载界面以解决此问题,并下载大文件(例如电影)。
大约 每。:此接口于2018年底实现,被称为Background Fetch,但到目前为止它仅在Chrome和Opera中有效(根据CanIUse的占68%)。
激活后
适用于删除旧的缓存和迁移。
在安装了新的工作程序并停止了旧的工作程序之后,新的工作程序被激活,并且我们收到一个激活事件。这是替换资源和删除旧缓存的绝好机会。
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys()
.then(cacheNames => Promise.all(
cacheNames.filter(cacheName => {
// true, , ,
// ,
}).map(cacheName => caches.delete(cacheName))
))
)
})
在激活期间,其他事件(例如访存)将排队,因此从理论上讲,长时间的激活可能会阻塞页面。因此,仅将此阶段用于您无法与老工人一起完成的事情。
发生自定义事件时
适合当整个网站无法脱机。在这种情况下,我们使用户能够决定要缓存的内容。例如,Youtube视频,Wikipedia页面或Flickr上的图片库。
向用户提供“稍后阅读”或“保存”按钮。单击该按钮后,获取资源并将其写入缓存。
document.querySelector('.cache-article').addEventListener('click', event => {
event.preventDefault()
const id = event.target.dataset.id
caches.open(`mysite-article ${id}`)
.then(cache => fetch(`/get-article-urls?id=${id}`)
.then(response => {
// get-article-urls JSON
// URL
return response.json()
}).then(urls => cache.addAll(urls)))
})
页面上可以使用缓存界面,就像工作程序本身一样,因此我们无需调用后者即可节省资源。
收到回应时
适用于频繁更新的资源,例如用户的邮箱或文章内容。也适合化身等次要内容,但在这种情况下要小心。
如果请求的资源不在缓存中,我们将从网络上获取它,将其发送到客户端,然后将其写入缓存。
如果您请求多个URL(例如头像路径),请确保该地址不会溢出原始存储(原始-协议,主机和端口)-如果用户需要释放磁盘空间,则您不应是第一个。请注意删除不必要的资源。
self.addEventListener('fetch', event => {
event.respondWith(
caches.open('mysite-dynamic')
.then(cache => cache.match(event.request)
.then(response => response || fetch(event.request)
.then(response => {
cache.put(event.request, response.clone())
return response
})))
)
})
为了有效地使用内存,我们只读取一次响应主体。上面的示例使用clone方法创建响应的副本。这样做是为了同时向客户端发送响应并将其写入缓存。
在检查新颖性期间
适用于更新不需要最新版本的资源。这也可以适用于化身。
如果资源在缓存中,我们将使用它,但是在下一个请求上获得更新。
self.addEventListener('fetch', event => {
event.respondWith(
caches.open('mysite-dynamic')
.then(cache => cache.match(event.request)
.then(response => {
const fetchPromise = fetch(event.request)
.then(networkResponse => {
cache.put(event.request, networkResponse.clone())
return networkResponse
})
return response || fetchPromise
}))
)
})
当您收到推送通知时
Push API是对worker的抽象。它允许工作程序响应来自操作系统的消息而运行。此外,无论用户如何,都会发生这种情况(关闭浏览器选项卡时)。页面通常会向用户发送请求,以请求执行某些操作。
适用于依赖通知的内容,例如聊天消息,新闻提要,电子邮件。也用于同步内容,例如列表中的任务或日历中的标记。
结果是一个通知,单击该通知将打开相应的页面。但是,在发送通知之前节省资源非常重要。用户在收到通知时处于联机状态,但是在单击通知时很可能处于脱机状态,因此在那时内容可以脱机使用很重要。Twitter移动应用程序这样做有点错误。
没有网络连接,Twitter不会提供与通知相关的内容。但是,单击通知将其删除。不要那样做!
以下代码在发送通知之前更新缓存:
self.addEventListener('push', event => {
if (event.data.text() === 'new-email') {
event.waitUntil(
caches.open('mysite-dynamic')
.then(cache => fetch('/inbox.json')
.then(response => {
cache.put('/inbox.json', response.clone())
return response.json()
})).then(emails => {
registration.showNotification('New email', {
body: `From ${emails[0].from.name}`,
tag: 'new-email'
})
})
)
}
})
self.addEventListener('notificationclick', event => {
if (event.notification.tag === 'new-email') {
// , , /inbox/ ,
// ,
new WindowClient('/inbox/')
}
})
具有后台同步
背景同步是对工作人员的另一种抽象。它允许您请求一次性或定期后台数据同步。它也独立于用户。但是,也会向他发送许可请求。
适用于更新微不足道的资源,定期发送通知的频率太高,因此对于用户来说很烦,例如,社交网络中的新事件或新闻提要中的新文章。
self.addEventListener('sync', event => {
if (event.id === 'update-leaderboard') {
event.waitUntil(
caches.open('mygame-dynamic')
.then(cache => cache.add('/leaderboard.json'))
)
}
})
保存缓存
您的源提供了一定数量的可用空间。该空间在所有存储之间共享:本地和会话,索引数据库,文件系统,当然还有缓存。
存储大小不是固定的,并且会因设备和存储条件而异。您可以像这样检查它:
navigator.storageQuota.queryInfo('temporary').then(info => {
console.log(info.quota)
// : < >
console.log(info.usage)
// < >
})
当此存储空间或该存储空间的大小达到限制时,将根据当前无法更改的某些规则清除该存储空间。
为了解决这个问题,提出了发送许可请求的接口(requestPersistent):
navigator.storage.requestPersistent().then(granted => {
if (granted) {
// ,
}
})
当然,用户必须为此授予权限。用户必须是此过程的一部分。如果用户设备上的内存已满,并且删除次要数据不能解决问题,则用户必须确定要保留和删除哪些数据。
为此,操作系统必须将浏览器存储区视为单独的项。
回答请求
缓存多少资源都没有关系,工作人员不会使用它,直到您告诉他什么时候使用什么。这是一些处理请求的模板。
现金支付
适用于页面当前版本的任何静态资源。您必须在工作程序设置阶段缓存这些资源,以便能够响应请求发送它们。
self.addEventListener('fetch', event => {
// ,
//
event.respondWith(caches.match(event.request))
})
仅网络
适用于无法缓存的资源,例如分析数据或非GET请求。
self.addEventListener('fetch', event => {
event.respondWith(fetch(event.request))
// event.respondWith
//
})
首先是高速缓存,然后是发生故障时的网络
适用于处理离线应用程序中的大多数请求。
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
)
})
保存的资源从缓存中返回,未保存的资源从网络中返回。
谁有时间他就吃
适用于小型资源,以追求低存储设备的更好性能。
旧的硬盘驱动器,防病毒软件和快速的Internet连接相结合,可以使从网络中获取数据的速度比从缓存中获取数据的速度更快。但是,在将数据存储在用户设备上的同时从网络检索数据会浪费资源。
// Promise.race ,
// .
//
const promiseAny = promises => new Promise((resolve, reject) => {
// promises
promises = promises.map(p => Promise.resolve(p))
// ,
promises.forEach(p => p.then(resolve))
// ,
promises.reduce((a, b) => a.catch(() => b))
.catch(() => reject(Error(' ')))
})
self.addEventListener('fetch', event => {
event.respondWith(
promiseAny([
caches.match(event.request),
fetch(event.request)
])
)
})
大约 Lane:现在您可以使用Promise.allSettled来实现此目的,但是其浏览器支持率为80%:-20%的用户可能太多了。
首先是网络,然后在出现故障时缓存
适用于经常更新且不会影响网站当前版本的资源,例如,文章,头像,社交网络上的新闻提要,播放器评分等。
这意味着您正在向在线用户提供新内容,向离线用户提供旧内容。如果来自网络的资源请求成功,则可能应该更新缓存。
这种方法有一个缺点。如果用户遇到连接问题或速度很慢,则他必须等待请求完成或失败,而不是立即从缓存中获取内容。此等待时间可能很长,导致糟糕的用户体验。
self.addEventListener('fetch', event => {
event.respondWith(
fetch(event.request).catch(() => caches.match(event.request))
)
})
首先是缓存,然后是网络
适用于频繁更新的资源。
这要求页面发送两个请求,一个用于高速缓存,一个用于网络。这个想法是从缓存中返回数据,然后在从网络接收数据时刷新它。
有时您可以在收到新数据时替换当前数据(例如,播放器的等级),但这对于大块内容来说是有问题的。这可能导致用户当前正在阅读或与之交互的内容消失。
Twitter在保持滚动的同时在现有内容之上添加了新内容:用户在屏幕顶部看到新推文的通知。这归功于内容的线性顺序。我复制了此模板,以尽快显示来自缓存的内容,并添加从网络获取的新内容。
页面上的代码:
const networkDataReceived = false
startSpinner()
//
const networkUpdate = fetch('/data.json')
.then(response => response.json())
.then(data => {
networkDataReceived = true
updatePage(data)
})
//
caches.match('/data.json')
.then(response => {
if (!response) throw Error(' ')
return response.json()
}).then(data => {
//
if (!networkDataReceived) {
updatePage(data)
}
}).catch(() => {
// , -
return networkUpdate
}).catch(showErrorMessage).then(stopSpinner)
工作人员代码:
我们访问网络并更新缓存。
self.addEventListener('fetch', event => {
event.respondWith(
caches.open('mysite-dynamic')
.then(cache => fetch(event.request)
.then(response => {
cache.put(event.request, response.clone())
return response
}))
)
})
安全网
如果尝试从缓存和网络获取资源失败,则必须进行回退。
适用于占位符(用虚拟图像替换图像),POST请求失败,“脱机时不可用”页面。
self.addEventListener('fetch', event => {
event.respondWith(
//
// ,
caches.match(event.request)
.then(response => response || fetch(event.request))
.catch(() => {
// ,
return caches.match('/offline.html')
//
// URL
})
)
})
如果您的页面提交了电子邮件,则工作人员可以在提交之前将其保存到索引数据库中,并通知页面提交失败,但是电子邮件已保存。
在工作端创建标记
适用于在服务器端呈现且无法缓存的页面。
服务器端页面渲染是一个非常快的过程,但是它使动态内容在缓存中的存储变得毫无意义,因为每个渲染可能有所不同。如果您的页面是由工作人员控制的,则您可以请求资源并在那里渲染页面。
import './templating-engine.js'
self.addEventListener('fetch', event => {
const requestURL = new URL(event.request.url)
event.respondWith(
Promise.all([
caches.match('/article-template.html')
.then(response => response.text()),
caches.match(`${requestURL.path}.json`)
.then(response => response.json())
]).then(responses => {
const template = responses[0]
const data = responses[1]
return new Response(renderTemplate(template, data), {
headers: {
'Content-Type': 'text/html'
}
})
})
)
})
一起
您不必局限于一个模板。您很可能必须根据要求将它们组合在一起。例如,训练有素的使用以下方法:
- 持久性UI元素的工作程序设置缓存
- 在服务器响应上缓存Flickr图像和数据
- 从高速缓存中检索数据,以及从网络中获取大多数请求失败时的数据
- 从缓存中检索资源,然后从Web检索Flick搜索结果的资源
只需查看请求并决定如何处理它:
self.addEventListener('fetch', event => {
// URL
const requestURL = new URL(event.request.url)
//
if (requestURL.hostname === 'api.example.com') {
event.respondWith(/* */)
return
}
//
if (requestURL.origin === location.origin) {
//
if (/^\/article\//.test(requestURL.pathname)) {
event.respondWith(/* */)
return
}
if (/\.webp$/.test(requestURL.pathname)) {
event.respondWith(/* */)
return
}
if (request.method == 'POST') {
event.respondWith(/* */)
return
}
if (/cheese/.test(requestURL.pathname)) {
event.respondWith(
// . .: - ?
new Response('Flagrant cheese error', {
//
status: 512
})
)
return
}
}
//
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
)
})
希望本文对您有所帮助。感谢您的关注。