* Fetch the given resource from the network, and cache it if able.
(req: Request, used: boolean = true)
| 298 | * Fetch the given resource from the network, and cache it if able. |
| 299 | */ |
| 300 | protected async fetchAndCacheOnce(req: Request, used: boolean = true): Promise<Response> { |
| 301 | // The `inFlightRequests` map holds information about which caching operations are currently |
| 302 | // underway for known resources. If this request appears there, another "thread" is already |
| 303 | // in the process of caching it, and this work should not be duplicated. |
| 304 | if (this.inFlightRequests.has(req.url)) { |
| 305 | // There is a caching operation already in progress for this request. Wait for it to |
| 306 | // complete, and hopefully it will have yielded a useful response. |
| 307 | return this.inFlightRequests.get(req.url)!; |
| 308 | } |
| 309 | |
| 310 | // No other caching operation is being attempted for this resource, so it will be owned here. |
| 311 | // Go to the network and get the correct version. |
| 312 | const fetchOp = this.fetchFromNetwork(req); |
| 313 | |
| 314 | // Save this operation in `inFlightRequests` so any other "thread" attempting to cache it |
| 315 | // will block on this chain instead of duplicating effort. |
| 316 | this.inFlightRequests.set(req.url, fetchOp); |
| 317 | |
| 318 | // Make sure this attempt is cleaned up properly on failure. |
| 319 | try { |
| 320 | // Wait for a response. If this fails, the request will remain in `inFlightRequests` |
| 321 | // indefinitely. |
| 322 | const res = await fetchOp; |
| 323 | |
| 324 | // It's very important that only successful responses are cached. Unsuccessful responses |
| 325 | // should never be cached as this can completely break applications. |
| 326 | if (!res.ok) { |
| 327 | throw new Error( |
| 328 | `Response not Ok (fetchAndCacheOnce): request for ${req.url} returned response ${res.status} ${res.statusText}`, |
| 329 | ); |
| 330 | } |
| 331 | |
| 332 | try { |
| 333 | // This response is safe to cache (as long as it's cloned). Wait until the cache operation |
| 334 | // is complete. |
| 335 | const cache = await this.cache; |
| 336 | await cache.put(req, res.clone()); |
| 337 | |
| 338 | // If the request is not hashed, update its metadata, especially the timestamp. This is |
| 339 | // needed for future determination of whether this cached response is stale or not. |
| 340 | if (!this.hashes.has(this.adapter.normalizeUrl(req.url))) { |
| 341 | // Metadata is tracked for requests that are unhashed. |
| 342 | const meta: UrlMetadata = {ts: this.adapter.time, used}; |
| 343 | const metaTable = await this.metadata; |
| 344 | await metaTable.write(req.url, meta); |
| 345 | } |
| 346 | |
| 347 | return res; |
| 348 | } catch (err) { |
| 349 | // Among other cases, this can happen when the user clears all data through the DevTools, |
| 350 | // but the SW is still running and serving another tab. In that case, trying to write to the |
| 351 | // caches throws an `Entry was not found` error. |
| 352 | // If this happens the SW can no longer work correctly. This situation is unrecoverable. |
| 353 | throw new SwCriticalError( |
| 354 | `Failed to update the caches for request to '${ |
| 355 | req.url |
| 356 | }' (fetchAndCacheOnce): ${errorToString(err)}`, |
| 357 | ); |
nothing calls this directly
no test coverage detected
searching dependent graphs…