StaleWhileRevalidate.ts
4.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
import {assert} from 'workbox-core/_private/assert.js';
import {logger} from 'workbox-core/_private/logger.js';
import {WorkboxError} from 'workbox-core/_private/WorkboxError.js';
import {cacheOkAndOpaquePlugin} from './plugins/cacheOkAndOpaquePlugin.js';
import {Strategy, StrategyOptions} from './Strategy.js';
import {StrategyHandler} from './StrategyHandler.js';
import {messages} from './utils/messages.js';
import './_version.js';
/**
* An implementation of a
* [stale-while-revalidate](https://developer.chrome.com/docs/workbox/caching-strategies-overview/#stale-while-revalidate)
* request strategy.
*
* Resources are requested from both the cache and the network in parallel.
* The strategy will respond with the cached version if available, otherwise
* wait for the network response. The cache is updated with the network response
* with each successful request.
*
* By default, this strategy will cache responses with a 200 status code as
* well as [opaque responses](https://developer.chrome.com/docs/workbox/caching-resources-during-runtime/#opaque-responses).
* Opaque responses are cross-origin requests where the response doesn't
* support [CORS](https://enable-cors.org/).
*
* If the network request fails, and there is no cache match, this will throw
* a `WorkboxError` exception.
*
* @extends workbox-strategies.Strategy
* @memberof workbox-strategies
*/
class StaleWhileRevalidate extends Strategy {
/**
* @param {Object} [options]
* @param {string} [options.cacheName] Cache name to store and retrieve
* requests. Defaults to cache names provided by
* {@link workbox-core.cacheNames}.
* @param {Array<Object>} [options.plugins] [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}
* to use in conjunction with this caching strategy.
* @param {Object} [options.fetchOptions] Values passed along to the
* [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)
* of [non-navigation](https://github.com/GoogleChrome/workbox/issues/1796)
* `fetch()` requests made by this strategy.
* @param {Object} [options.matchOptions] [`CacheQueryOptions`](https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions)
*/
constructor(options: StrategyOptions = {}) {
super(options);
// If this instance contains no plugins with a 'cacheWillUpdate' callback,
// prepend the `cacheOkAndOpaquePlugin` plugin to the plugins list.
if (!this.plugins.some((p) => 'cacheWillUpdate' in p)) {
this.plugins.unshift(cacheOkAndOpaquePlugin);
}
}
/**
* @private
* @param {Request|string} request A request to run this strategy for.
* @param {workbox-strategies.StrategyHandler} handler The event that
* triggered the request.
* @return {Promise<Response>}
*/
async _handle(request: Request, handler: StrategyHandler): Promise<Response> {
const logs = [];
if (process.env.NODE_ENV !== 'production') {
assert!.isInstance(request, Request, {
moduleName: 'workbox-strategies',
className: this.constructor.name,
funcName: 'handle',
paramName: 'request',
});
}
const fetchAndCachePromise = handler.fetchAndCachePut(request).catch(() => {
// Swallow this error because a 'no-response' error will be thrown in
// main handler return flow. This will be in the `waitUntil()` flow.
});
void handler.waitUntil(fetchAndCachePromise);
let response = await handler.cacheMatch(request);
let error;
if (response) {
if (process.env.NODE_ENV !== 'production') {
logs.push(
`Found a cached response in the '${this.cacheName}'` +
` cache. Will update with the network response in the background.`,
);
}
} else {
if (process.env.NODE_ENV !== 'production') {
logs.push(
`No response found in the '${this.cacheName}' cache. ` +
`Will wait for the network response.`,
);
}
try {
// NOTE(philipwalton): Really annoying that we have to type cast here.
// https://github.com/microsoft/TypeScript/issues/20006
response = (await fetchAndCachePromise) as Response | undefined;
} catch (err) {
if (err instanceof Error) {
error = err;
}
}
}
if (process.env.NODE_ENV !== 'production') {
logger.groupCollapsed(
messages.strategyStart(this.constructor.name, request),
);
for (const log of logs) {
logger.log(log);
}
messages.printFinalResponse(response);
logger.groupEnd();
}
if (!response) {
throw new WorkboxError('no-response', {url: request.url, error});
}
return response;
}
}
export {StaleWhileRevalidate};