StaleWhileRevalidate.js 5.1 KB

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