CacheFirst.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  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 { cacheNames } from 'workbox-core/_private/cacheNames.js';
  9. import { cacheWrapper } from 'workbox-core/_private/cacheWrapper.js';
  10. import { fetchWrapper } from 'workbox-core/_private/fetchWrapper.js';
  11. import { getFriendlyURL } from 'workbox-core/_private/getFriendlyURL.js';
  12. import { logger } from 'workbox-core/_private/logger.js';
  13. import { WorkboxError } from 'workbox-core/_private/WorkboxError.js';
  14. import { messages } from './utils/messages.js';
  15. import './_version.js';
  16. /**
  17. * An implementation of a [cache-first]{@link https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#cache-falling-back-to-network}
  18. * request strategy.
  19. *
  20. * A cache first strategy is useful for assets that have been revisioned,
  21. * such as URLs like `/styles/example.a8f5f1.css`, since they
  22. * can be cached for long periods of time.
  23. *
  24. * If the network request fails, and there is no cache match, this will throw
  25. * a `WorkboxError` exception.
  26. *
  27. * @memberof module:workbox-strategies
  28. */
  29. class CacheFirst {
  30. /**
  31. * @param {Object} options
  32. * @param {string} options.cacheName Cache name to store and retrieve
  33. * requests. Defaults to cache names provided by
  34. * [workbox-core]{@link module:workbox-core.cacheNames}.
  35. * @param {Array<Object>} options.plugins [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}
  36. * to use in conjunction with this caching strategy.
  37. * @param {Object} options.fetchOptions Values passed along to the
  38. * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)
  39. * of all fetch() requests made by this strategy.
  40. * @param {Object} options.matchOptions [`CacheQueryOptions`](https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions)
  41. */
  42. constructor(options = {}) {
  43. this._cacheName = cacheNames.getRuntimeName(options.cacheName);
  44. this._plugins = options.plugins || [];
  45. this._fetchOptions = options.fetchOptions;
  46. this._matchOptions = options.matchOptions;
  47. }
  48. /**
  49. * This method will perform a request strategy and follows an API that
  50. * will work with the
  51. * [Workbox Router]{@link module:workbox-routing.Router}.
  52. *
  53. * @param {Object} options
  54. * @param {Request|string} options.request A request to run this strategy for.
  55. * @param {Event} [options.event] The event that triggered the request.
  56. * @return {Promise<Response>}
  57. */
  58. async handle({ event, request }) {
  59. const logs = [];
  60. if (typeof request === 'string') {
  61. request = new Request(request);
  62. }
  63. if (process.env.NODE_ENV !== 'production') {
  64. assert.isInstance(request, Request, {
  65. moduleName: 'workbox-strategies',
  66. className: 'CacheFirst',
  67. funcName: 'makeRequest',
  68. paramName: 'request',
  69. });
  70. }
  71. let response = await cacheWrapper.match({
  72. cacheName: this._cacheName,
  73. request,
  74. event,
  75. matchOptions: this._matchOptions,
  76. plugins: this._plugins,
  77. });
  78. let error;
  79. if (!response) {
  80. if (process.env.NODE_ENV !== 'production') {
  81. logs.push(`No response found in the '${this._cacheName}' cache. ` +
  82. `Will respond with a network request.`);
  83. }
  84. try {
  85. response = await this._getFromNetwork(request, event);
  86. }
  87. catch (err) {
  88. error = err;
  89. }
  90. if (process.env.NODE_ENV !== 'production') {
  91. if (response) {
  92. logs.push(`Got response from network.`);
  93. }
  94. else {
  95. logs.push(`Unable to get a response from the network.`);
  96. }
  97. }
  98. }
  99. else {
  100. if (process.env.NODE_ENV !== 'production') {
  101. logs.push(`Found a cached response in the '${this._cacheName}' cache.`);
  102. }
  103. }
  104. if (process.env.NODE_ENV !== 'production') {
  105. logger.groupCollapsed(messages.strategyStart('CacheFirst', request));
  106. for (const log of logs) {
  107. logger.log(log);
  108. }
  109. messages.printFinalResponse(response);
  110. logger.groupEnd();
  111. }
  112. if (!response) {
  113. throw new WorkboxError('no-response', { url: request.url, error });
  114. }
  115. return response;
  116. }
  117. /**
  118. * Handles the network and cache part of CacheFirst.
  119. *
  120. * @param {Request} request
  121. * @param {Event} [event]
  122. * @return {Promise<Response>}
  123. *
  124. * @private
  125. */
  126. async _getFromNetwork(request, event) {
  127. const response = await fetchWrapper.fetch({
  128. request,
  129. event,
  130. fetchOptions: this._fetchOptions,
  131. plugins: this._plugins,
  132. });
  133. // Keep the service worker while we put the request to the cache
  134. const responseClone = response.clone();
  135. const cachePutPromise = cacheWrapper.put({
  136. cacheName: this._cacheName,
  137. request,
  138. response: responseClone,
  139. event,
  140. plugins: this._plugins,
  141. });
  142. if (event) {
  143. try {
  144. event.waitUntil(cachePutPromise);
  145. }
  146. catch (error) {
  147. if (process.env.NODE_ENV !== 'production') {
  148. logger.warn(`Unable to ensure service worker stays alive when ` +
  149. `updating cache for '${getFriendlyURL(request.url)}'.`);
  150. }
  151. }
  152. }
  153. return response;
  154. }
  155. }
  156. export { CacheFirst };