Service Worker中使用缓存API
Service Worker API带有 Cache接口,该接口可让您创建按请求键控的响应存储。尽管此接口是为service workers设计的,但实际上它是暴露在窗口中的,可以从脚本中的任何位置进行访问。入口点是caches
。
您负责实现脚本(Service Worker)如何处理对缓存的更新。必须明确请求对缓存中所有项目的更新;项不会过期,必须删除。但是,如果缓存的数据量超过浏览器的存储限制,则浏览器将开始逐出与一个起点(一次一个起点)关联的所有数据,直到存储量再次低于该极限。有关更多信息,请参见 浏览器存储限制和限制条件。
储存资源
在本节中,我们概述了一些用于缓存资源的常见模式: Service Worker安装, 用户交互和 网络响应。
安装时-缓存应用程序框架
如果install
启动service worker ,我们可以缓存HTML,CSS,JS以及组成应用程序框架的任何静态文件:
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(cacheName).then(function(cache) {
return cache.addAll(
[
'/css/bootstrap.css',
'/css/main.css',
'/js/bootstrap.min.js',
'/js/jquery.min.js',
'/offline.html'
]
);
})
);
});
首次安装Service Worker时,将触发此事件侦听器。
注意:,在发生此事件时,任何以前版本的Service Worker仍在运行并提供页面,因此您在此处所做的事情一定不能破坏它。例如,这不是删除旧缓存的好地方,因为以前的Service Worker此时可能仍在使用它们。
event.waitUntil
延长install
事件的生存期,直到传递的承诺成功解决为止。如果承诺被拒绝,则安装被视为失败,并且该服务工作程序被放弃(如果运行的是旧版本,它将保持活动状态)。
cache.addAll
如果任何资源无法缓存,将拒绝。这意味着仅当所有资源cache.addAll
都已缓存时,Service Worker才会安装。
关于用户交互
如果无法使整个网站离线,则可以让用户选择他们希望离线使用的内容(例如,视频,文章或图片库)。
一种方法是为用户提供“稍后阅读”或“保存为离线”按钮。单击它后,从网络中获取您所需的内容并将其放入缓存中:
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-article
该类的元素时,我们将获取商品ID,获取具有该ID的商品,并将该商品添加到缓存中。
注意: 缓存API在窗口对象上可用,这意味着您不需要让Service Worker将东西添加到缓存中。
关于网络响应
如果请求与缓存中的任何内容都不匹配,请从网络获取请求,将其发送到页面,然后同时将其添加到缓存中。
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;
});
});
})
);
});
这种方法最适合经常更新的资源,例如用户的收件箱或文章内容。这对于非必要内容(例如化身)也很有用,但需要小心。如果对一系列URL执行此操作,请注意不要膨胀原始存储-如果用户需要回收磁盘空间,则您不希望成为主要候选人。确保您摆脱了不再需要的缓存中的项目。
注意: 为了有效利用内存,您只能读取一次响应/请求的正文。在上面的代码中,
.clone()
用于创建可单独读取的响应副本。
从缓存中提取文件
要提供缓存中的内容并使您的应用离线使用,您需要拦截网络请求并使用缓存中存储的文件进行响应。有几种方法可以解决此问题:
- 仅缓存
- 仅网络
- 缓存回落到网络
- 网络回退到缓存
- 然后缓存网络
仅缓存
您不需要经常专门处理这种情况。 缓存回落到网络通常是适当的方法。
这种方法适用于应用程序主代码(应用程序“版本”的一部分)中的任何静态资产。您应该已经将它们缓存在install事件中,因此您可以依靠它们。
self.addEventListener('fetch', function(event) {
event.respondWith(caches.match(event.request));
});
如果在缓存中找不到匹配项,则响应将看起来像是连接错误。
仅网络
对于无法离线执行的操作(例如分析ping和非GET请求),这是正确的方法。同样,您不必经常专门处理这种情况,并且 缓存回落到网络方法通常更合适。
self.addEventListener('fetch', function(event) {
event.respondWith(fetch(event.request));
});
或者,只需不调用event.respondWith
,这将导致默认的浏览器行为。
缓存回落到网络
如果您要先使应用程序离线,那么这就是您处理大多数请求的方式。其他模式将是基于传入请求的异常。
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request).then(function(response) {
return response || fetch(event.request);
})
);
});
这使您对缓存中的内容具有“仅缓存”行为,对于未缓存的任何事物(包括所有非GET请求,因为它们无法缓存),其为“仅网络”行为。
网络回退到缓存
对于频繁更新且不属于网站“版本”一部分的资源(例如,文章,头像,社交媒体时间表,游戏排行榜),这是一种很好的方法。以这种方式处理网络请求意味着在线用户获得最新内容,而离线用户获得较旧的缓存版本。
但是,这种方法有缺陷。如果用户的连接是间歇性的或速度较慢,则他们必须等待网络出现故障,然后才能从缓存中获取内容。这可能会花费很长时间,并且是令人沮丧的用户体验。有关更好的解决方案,请参见下一种方法,即“先 缓存然后是网络”。
self.addEventListener('fetch', function(event) {
event.respondWith(
fetch(event.request).catch(function() {
return caches.match(event.request);
})
);
});
在这里,我们首先使用将该请求发送到网络fetch()
,只有在失败时,我们才会在缓存中寻找响应。
缓存然后网络
对于频繁更新的资源,这也是一个很好的方法。这种方法将尽可能快地将内容显示在屏幕上,但到达后仍会显示最新内容。
这要求页面发出两个请求:一个请求到缓存,另一个请求到网络。这个想法是先显示缓存的数据,然后在/如果网络数据到达时更新页面。
这是页面中的代码:
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());
我们正在向网络和缓存发送请求。缓存很可能会首先响应,如果尚未接收到网络数据,我们将使用响应中的数据更新页面。当网络响应时,我们将使用最新信息再次更新页面。
这是Service Worker中的代码:
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;
});
})
);
});
这将在获取网络响应时对其进行缓存。
有时,您可以在新数据到达时替换当前数据(例如,游戏排行榜),但请注意不要隐藏或替换用户可能正在与之交互的内容。例如,如果从缓存中加载博客文章页面,然后在从网络中获取新文章时将其添加到页面顶部,则可以考虑调整滚动位置,以使用户不受干扰。如果您的应用程序布局是线性的,那么这可能是一个很好的解决方案。
通用回落
如果您无法通过缓存和/或网络提供服务,则可能需要提供通用的备用。此技术非常适合辅助图像,例如头像,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 & headers.
// Eg, a fallback silhouette image for avatars.
})
);
});
您回退到的项目可能是安装依赖项。
您还可以根据网络错误提供不同的后备:
self.addEventListener('fetch', function(event) {
event.respondWith(
// Try the cache
caches.match(event.request).then(function(response) {
if (response) {
return response;
}
return fetch(event.request).then(function(response) {
if (response.status === 404) {
return caches.match('pages/404.html');
}
return response
});
}).catch(function() {
// If both fail, show a generic fallback:
return caches.match('/offline.html');
})
);
});
网络响应错误不会在fetch
承诺中引发错误。而是fetch
返回包含网络错误的错误代码的响应对象。这意味着我们用a.then
代替a处理网络错误.catch
。
删除过时的缓存
一旦安装了新的Service Worker并且未使用以前的版本,则将激活新的Service Worker,并且您会收到一个activate
事件。由于旧版本已无法使用,因此现在是删除未使用的缓存的好时机。
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
放入队列中),因此长时间激活可能会阻止页面加载。保持激活尽可能精简,仅将其用于旧版本处于活动状态时无法执行的操作。
使用缓存API
在这里,我们介绍了Cache API的属性和方法。
检查支持
我们可以检查浏览器是否支持Cache API,如下所示:
if ('caches' in window) {
// has support
}
创建缓存
一个源可以有多个命名的Cache对象。要创建缓存或打开与现有缓存的连接,我们使用的 caches.open
方法。
caches.open(cacheName)
这将返回一个承诺,该承诺将解析为缓存对象。caches.open
接受将是缓存名称的字符串。
处理数据
缓存API带有多种方法,可让我们在缓存中创建和操作数据。这些可以分为创建,匹配或删除数据的方法。
建立资料
我们可以使用三种方法将数据添加到缓存。这些 add
, addAll
和 put
。实际上,我们将在从返回的缓存对象上调用这些方法caches.open()
。例如:
caches.open('example-cache').then(function(cache) {
cache.add('/example-file.html');
});
Caches.open
返回example-cache
Cache对象,该对象将传递到中的回调.then
。我们add
在此对象上调用方法以将文件添加到该缓存。
cache.add(request)
-add方法获取URL,对其进行检索,然后将所得的响应对象添加到给定的缓存中。该对象的关键是请求,因此我们稍后可以通过该请求再次检索该响应对象。
cache.addAll(requests)
-此方法与add相同,除了它采用URL数组并将其添加到缓存中。如果无法将任何文件添加到缓存中,则整个操作将失败,并且不会添加任何文件。
cache.put(request, response)
-此方法同时接收请求和响应对象,并将它们添加到缓存中。这使您可以手动插入响应对象。通常,您只需要fetch()
一个或多个请求,然后将结果直接添加到缓存中。在这种情况下,最好cache.add
还是使用或cache.addAll
,因为它们是以下一项或多项操作的简写功能:
fetch(url).then(function (response) {
return cache.put(url, response);
})
匹配数据
有两种方法可以在缓存中搜索特定内容: match
和 matchAll
。可以在caches
对象上调用这些,以搜索所有现有的缓存,也可以在从返回的特定缓存上进行搜索caches.open()
。
caches.match(request, options)
-此方法返回一个Promise,该Promise解析为与一个或多个缓存中的第一个匹配请求关联的响应对象。undefined
如果找不到匹配项,则返回。第一个参数是请求,第二个参数是用于优化搜索的可选选项列表。这是MDN定义的选项:
-
ignoreSearch
:一个布尔值,指定是否忽略URL中的查询字符串。例如,如果设置为true
的?value=bar
一部分,则http://foo.com/?value=bar
在执行匹配时将被忽略。默认为false
。 -
ignoreMethod
:一个布尔值,当设置true
为时,阻止匹配操作验证Request HTTP方法(通常仅允许GET和HEAD。)它默认为false。 -
ignoreVary
:一个布尔值,当设置true
为时告诉匹配操作不要执行VARY标头匹配-也就是说,如果URL匹配,则无论Response对象是否具有VARY标头,您都将获得一个匹配项。默认为false
。 -
cacheName
:一个DOMString,表示要在其中进行搜索的特定缓存。请注意,此选项被忽略Cache.match()
。
caches.matchAll(request, options)
-此方法与.match
其他方法相同,只是它从缓存中返回所有匹配的响应,而不仅仅是第一个。例如,如果您的应用程序缓存了图像文件夹中包含的某些图像,我们可以返回所有图像并对它们执行一些操作,如下所示:
caches.open('example-cache').then(function(cache) {
cache.matchAll('/images/').then(function(response) {
response.forEach(function(element, index, array) {
cache.delete(element);
});
});
})
删除资料
我们可以使用删除缓存中的项目cache.delete(request, options)
。此方法在缓存中找到与请求匹配的项目,将其删除,然后返回解析为的Promise true
。如果找不到该项目,则将其解析为false。它还具有与match方法相同的可选options参数。
检索密钥
最后,我们可以使用获取缓存键列表cache.keys(request, options)
。这将返回一个Promise,该Promise解析为一组缓存键。这些将以插入缓存中的相同顺序返回。这两个参数都是可选的。如果未传递任何内容,则cache.keys
返回缓存中的所有请求。如果传递了请求,它将从缓存中返回所有匹配的请求。这些选项与先前方法中的选项相同。
还可以在缓存入口点上调用keys方法,以返回缓存本身的密钥。这使您可以一次性清除过时的缓存。
网友评论