/* 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} [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} */ async _handle(request: Request, handler: StrategyHandler): Promise { 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};