* @param {DispatchFn} dispatch * @param {import('../../types/cache-interceptor.d.ts').default.CacheHandlerOptions} globalOpts * @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} cacheKey * @param {import('../../types/dispatcher.d.ts').default.DispatchHandler} handler * @para
( dispatch, globalOpts, cacheKey, handler, opts, reqCacheControl, result )
| 261 | * @param {import('../../types/cache-interceptor.d.ts').default.GetResult | undefined} result |
| 262 | */ |
| 263 | function handleResult ( |
| 264 | dispatch, |
| 265 | globalOpts, |
| 266 | cacheKey, |
| 267 | handler, |
| 268 | opts, |
| 269 | reqCacheControl, |
| 270 | result |
| 271 | ) { |
| 272 | if (!result) { |
| 273 | return handleUncachedResponse(dispatch, globalOpts, cacheKey, handler, opts, reqCacheControl) |
| 274 | } |
| 275 | |
| 276 | const now = Date.now() |
| 277 | if (now > result.deleteAt) { |
| 278 | // Response is expired, cache store shouldn't have given this to us |
| 279 | return dispatch(opts, new CacheHandler(globalOpts, cacheKey, handler)) |
| 280 | } |
| 281 | |
| 282 | const age = Math.round((now - result.cachedAt) / 1000) |
| 283 | if (reqCacheControl?.['max-age'] && age >= reqCacheControl['max-age']) { |
| 284 | // Response is considered expired for this specific request |
| 285 | // https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1.1 |
| 286 | return dispatch(opts, handler) |
| 287 | } |
| 288 | |
| 289 | const stale = isStale(result, reqCacheControl) |
| 290 | const revalidate = needsRevalidation(result, reqCacheControl, opts) |
| 291 | |
| 292 | // Check if the response is stale |
| 293 | if (stale || revalidate) { |
| 294 | if (util.isStream(opts.body) && util.bodyLength(opts.body) !== 0) { |
| 295 | // If body is a stream we can't revalidate... |
| 296 | // TODO (fix): This could be less strict... |
| 297 | return dispatch(opts, new CacheHandler(globalOpts, cacheKey, handler)) |
| 298 | } |
| 299 | |
| 300 | // RFC 5861: If we're within stale-while-revalidate window, serve stale immediately |
| 301 | // and revalidate in background, unless immediate revalidation is necessary |
| 302 | if (!revalidate && withinStaleWhileRevalidateWindow(result)) { |
| 303 | // Serve stale response immediately |
| 304 | sendCachedValue(handler, opts, result, age, null, true) |
| 305 | |
| 306 | // Start background revalidation (fire-and-forget) |
| 307 | queueMicrotask(() => { |
| 308 | const headers = { |
| 309 | ...opts.headers, |
| 310 | 'if-modified-since': new Date(result.cachedAt).toUTCString() |
| 311 | } |
| 312 | |
| 313 | if (result.etag) { |
| 314 | headers['if-none-match'] = result.etag |
| 315 | } |
| 316 | |
| 317 | if (result.vary) { |
| 318 | for (const key in result.vary) { |
| 319 | if (result.vary[key] != null) { |
| 320 | headers[key] = result.vary[key] |
no test coverage detected