2021-09-04 16:41:06 +02:00
|
|
|
import RenderResult from './render-result'
|
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
|
|
|
|
2022-02-09 00:46:59 +01:00
|
|
|
export interface CachedRedirectValue {
|
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
|
|
|
kind: 'REDIRECT'
|
|
|
|
props: Object
|
|
|
|
}
|
|
|
|
|
|
|
|
interface CachedPageValue {
|
|
|
|
kind: 'PAGE'
|
2022-02-09 00:46:59 +01:00
|
|
|
// this needs to be a RenderResult so since renderResponse
|
|
|
|
// expects that type instead of a string
|
Add `RenderResult` (#27319)
Adds `RenderResult`, replacing the `string` that `renderToHTML` used to return, with an `Observable`-like API that callers can use to subscribe and get a callback when chunks are available to flush, etc.
This is the last architectural change needed for streaming. There are, however, other things currently standing in the way of streaming. For example, it is common to mutate `res` in `getServerSideProps` to do routing work, or write headers, before fetching page data. This pattern effectively nullifies any advantages of streaming. I may do a follow-up PR that adds an experimental alternative for applications not using React 18, but the main purpose for this support is for Suspense and Server Components.
For that reason, there's no actual streaming here yet: instead we just flush a single chunk. A follow-up PR will add support for streaming suspense boundaries in React 18.
2021-07-27 21:18:21 +02:00
|
|
|
html: RenderResult
|
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
|
|
|
pageData: Object
|
|
|
|
}
|
|
|
|
|
2022-02-09 00:46:59 +01:00
|
|
|
export interface CachedImageValue {
|
|
|
|
kind: 'IMAGE'
|
|
|
|
etag: string
|
|
|
|
buffer: Buffer
|
|
|
|
extension: string
|
|
|
|
isMiss?: boolean
|
|
|
|
isStale?: boolean
|
|
|
|
}
|
|
|
|
|
|
|
|
interface IncrementalCachedPageValue {
|
|
|
|
kind: 'PAGE'
|
|
|
|
// this needs to be a string since the cache expects to store
|
|
|
|
// the string value
|
|
|
|
html: string
|
|
|
|
pageData: Object
|
|
|
|
}
|
|
|
|
|
|
|
|
export type IncrementalCacheEntry = {
|
|
|
|
curRevalidate?: number | false
|
|
|
|
// milliseconds to revalidate after
|
|
|
|
revalidateAfter: number | false
|
|
|
|
isStale?: boolean
|
|
|
|
value: IncrementalCacheValue | null
|
|
|
|
}
|
|
|
|
|
|
|
|
export type IncrementalCacheValue =
|
|
|
|
| CachedRedirectValue
|
|
|
|
| IncrementalCachedPageValue
|
|
|
|
| CachedImageValue
|
|
|
|
|
|
|
|
export type ResponseCacheValue =
|
|
|
|
| CachedRedirectValue
|
|
|
|
| CachedPageValue
|
|
|
|
| CachedImageValue
|
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
|
|
|
|
|
|
|
export type ResponseCacheEntry = {
|
|
|
|
revalidate?: number | false
|
|
|
|
value: ResponseCacheValue | null
|
2022-02-09 00:46:59 +01:00
|
|
|
isStale?: boolean
|
|
|
|
isMiss?: boolean
|
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
|
|
|
}
|
|
|
|
|
2021-07-22 23:04:58 +02:00
|
|
|
type ResponseGenerator = (
|
2022-02-08 04:50:23 +01:00
|
|
|
hasResolved: boolean,
|
|
|
|
hadCache: boolean
|
2021-07-22 23:04:58 +02:00
|
|
|
) => Promise<ResponseCacheEntry | null>
|
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
|
|
|
|
2022-03-02 23:09:40 +01:00
|
|
|
type IncrementalCacheItem = {
|
|
|
|
revalidateAfter?: number | false
|
|
|
|
curRevalidate?: number | false
|
|
|
|
revalidate?: number | false
|
|
|
|
value: IncrementalCacheValue | null
|
|
|
|
isStale?: boolean
|
|
|
|
isMiss?: boolean
|
|
|
|
} | null
|
|
|
|
|
2022-02-09 00:46:59 +01:00
|
|
|
interface IncrementalCache {
|
2022-03-02 23:09:40 +01:00
|
|
|
get: (key: string) => Promise<IncrementalCacheItem>
|
2022-02-09 00:46:59 +01:00
|
|
|
set: (
|
|
|
|
key: string,
|
|
|
|
data: IncrementalCacheValue | null,
|
|
|
|
revalidate?: number | false
|
|
|
|
) => Promise<void>
|
|
|
|
}
|
|
|
|
|
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
|
|
|
export default class ResponseCache {
|
|
|
|
incrementalCache: IncrementalCache
|
2021-07-22 23:04:58 +02:00
|
|
|
pendingResponses: Map<string, Promise<ResponseCacheEntry | null>>
|
2022-03-03 00:06:54 +01:00
|
|
|
previousCacheItem?: {
|
|
|
|
key: string
|
|
|
|
entry: ResponseCacheEntry | null
|
|
|
|
expiresAt: number
|
|
|
|
}
|
|
|
|
minimalMode?: boolean
|
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
|
|
|
|
2022-03-03 00:06:54 +01:00
|
|
|
constructor(incrementalCache: IncrementalCache, minimalMode: boolean) {
|
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
|
|
|
this.incrementalCache = incrementalCache
|
|
|
|
this.pendingResponses = new Map()
|
2022-03-03 00:06:54 +01:00
|
|
|
this.minimalMode = minimalMode
|
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public get(
|
|
|
|
key: string | null,
|
2022-02-08 04:50:23 +01:00
|
|
|
responseGenerator: ResponseGenerator,
|
2022-03-03 00:06:54 +01:00
|
|
|
context: {
|
|
|
|
isManualRevalidate?: boolean
|
|
|
|
}
|
2021-07-22 23:04:58 +02:00
|
|
|
): Promise<ResponseCacheEntry | null> {
|
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
|
|
|
const pendingResponse = key ? this.pendingResponses.get(key) : null
|
|
|
|
if (pendingResponse) {
|
|
|
|
return pendingResponse
|
|
|
|
}
|
|
|
|
|
2021-07-22 23:04:58 +02:00
|
|
|
let resolver: (cacheEntry: ResponseCacheEntry | null) => void = () => {}
|
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
|
|
|
let rejecter: (error: Error) => void = () => {}
|
2021-07-22 23:04:58 +02:00
|
|
|
const promise: Promise<ResponseCacheEntry | null> = new Promise(
|
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
|
|
|
(resolve, reject) => {
|
|
|
|
resolver = resolve
|
|
|
|
rejecter = reject
|
|
|
|
}
|
|
|
|
)
|
|
|
|
if (key) {
|
|
|
|
this.pendingResponses.set(key, promise)
|
|
|
|
}
|
|
|
|
|
|
|
|
let resolved = false
|
2021-07-22 23:04:58 +02:00
|
|
|
const resolve = (cacheEntry: ResponseCacheEntry | null) => {
|
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
|
|
|
if (key) {
|
|
|
|
// Ensure all reads from the cache get the latest value.
|
|
|
|
this.pendingResponses.set(key, Promise.resolve(cacheEntry))
|
|
|
|
}
|
|
|
|
if (!resolved) {
|
|
|
|
resolved = true
|
|
|
|
resolver(cacheEntry)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-03 00:06:54 +01:00
|
|
|
// we keep the previous cache entry around to leverage
|
|
|
|
// when the incremental cache is disabled in minimal mode
|
|
|
|
if (
|
|
|
|
key &&
|
|
|
|
this.minimalMode &&
|
|
|
|
this.previousCacheItem?.key === key &&
|
|
|
|
this.previousCacheItem.expiresAt > Date.now()
|
|
|
|
) {
|
|
|
|
resolve(this.previousCacheItem.entry)
|
|
|
|
this.pendingResponses.delete(key)
|
|
|
|
return promise
|
|
|
|
}
|
|
|
|
|
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
|
|
|
// We wait to do any async work until after we've added our promise to
|
|
|
|
// `pendingResponses` to ensure that any any other calls will reuse the
|
|
|
|
// same promise until we've fully finished our work.
|
|
|
|
;(async () => {
|
2022-03-02 23:09:40 +01:00
|
|
|
let cachedResponse: IncrementalCacheItem = null
|
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
|
|
|
try {
|
2022-03-03 00:06:54 +01:00
|
|
|
cachedResponse =
|
|
|
|
key && !this.minimalMode ? await this.incrementalCache.get(key) : null
|
|
|
|
|
2022-02-15 21:22:15 +01:00
|
|
|
if (cachedResponse && !context.isManualRevalidate) {
|
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
|
|
|
resolve({
|
2022-02-09 00:46:59 +01:00
|
|
|
isStale: cachedResponse.isStale,
|
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
|
|
|
revalidate: cachedResponse.curRevalidate,
|
Add `RenderResult` (#27319)
Adds `RenderResult`, replacing the `string` that `renderToHTML` used to return, with an `Observable`-like API that callers can use to subscribe and get a callback when chunks are available to flush, etc.
This is the last architectural change needed for streaming. There are, however, other things currently standing in the way of streaming. For example, it is common to mutate `res` in `getServerSideProps` to do routing work, or write headers, before fetching page data. This pattern effectively nullifies any advantages of streaming. I may do a follow-up PR that adds an experimental alternative for applications not using React 18, but the main purpose for this support is for Suspense and Server Components.
For that reason, there's no actual streaming here yet: instead we just flush a single chunk. A follow-up PR will add support for streaming suspense boundaries in React 18.
2021-07-27 21:18:21 +02:00
|
|
|
value:
|
|
|
|
cachedResponse.value?.kind === 'PAGE'
|
|
|
|
? {
|
|
|
|
kind: 'PAGE',
|
2021-09-04 16:41:06 +02:00
|
|
|
html: RenderResult.fromStatic(cachedResponse.value.html),
|
Add `RenderResult` (#27319)
Adds `RenderResult`, replacing the `string` that `renderToHTML` used to return, with an `Observable`-like API that callers can use to subscribe and get a callback when chunks are available to flush, etc.
This is the last architectural change needed for streaming. There are, however, other things currently standing in the way of streaming. For example, it is common to mutate `res` in `getServerSideProps` to do routing work, or write headers, before fetching page data. This pattern effectively nullifies any advantages of streaming. I may do a follow-up PR that adds an experimental alternative for applications not using React 18, but the main purpose for this support is for Suspense and Server Components.
For that reason, there's no actual streaming here yet: instead we just flush a single chunk. A follow-up PR will add support for streaming suspense boundaries in React 18.
2021-07-27 21:18:21 +02:00
|
|
|
pageData: cachedResponse.value.pageData,
|
|
|
|
}
|
|
|
|
: cachedResponse.value,
|
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
|
|
|
})
|
|
|
|
if (!cachedResponse.isStale) {
|
|
|
|
// The cached value is still valid, so we don't need
|
|
|
|
// to update it yet.
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-08 04:50:23 +01:00
|
|
|
const cacheEntry = await responseGenerator(resolved, !!cachedResponse)
|
2022-02-09 00:46:59 +01:00
|
|
|
resolve(
|
|
|
|
cacheEntry === null
|
|
|
|
? null
|
|
|
|
: {
|
|
|
|
...cacheEntry,
|
|
|
|
isMiss: !cachedResponse,
|
|
|
|
}
|
|
|
|
)
|
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
|
|
|
|
2021-07-22 23:04:58 +02:00
|
|
|
if (key && cacheEntry && typeof cacheEntry.revalidate !== 'undefined') {
|
2022-03-03 00:06:54 +01:00
|
|
|
if (this.minimalMode) {
|
|
|
|
this.previousCacheItem = {
|
|
|
|
key,
|
|
|
|
entry: cacheEntry,
|
|
|
|
expiresAt:
|
|
|
|
typeof cacheEntry.revalidate !== 'number'
|
|
|
|
? Date.now() + 1000
|
|
|
|
: Date.now() + cacheEntry?.revalidate * 1000,
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
await this.incrementalCache.set(
|
|
|
|
key,
|
|
|
|
cacheEntry.value?.kind === 'PAGE'
|
|
|
|
? {
|
|
|
|
kind: 'PAGE',
|
|
|
|
html: cacheEntry.value.html.toUnchunkedString(),
|
|
|
|
pageData: cacheEntry.value.pageData,
|
|
|
|
}
|
|
|
|
: cacheEntry.value,
|
|
|
|
cacheEntry.revalidate
|
|
|
|
)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.previousCacheItem = undefined
|
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
|
|
|
}
|
|
|
|
} catch (err) {
|
2022-03-02 23:09:40 +01:00
|
|
|
// when a getStaticProps path is erroring we automatically re-set the
|
|
|
|
// existing cache under a new expiration to prevent non-stop retrying
|
|
|
|
if (cachedResponse && key) {
|
|
|
|
await this.incrementalCache.set(
|
|
|
|
key,
|
|
|
|
cachedResponse.value,
|
|
|
|
Math.min(Math.max(cachedResponse.revalidate || 3, 3), 30)
|
|
|
|
)
|
|
|
|
}
|
2022-01-05 20:40:04 +01:00
|
|
|
// while revalidating in the background we can't reject as
|
|
|
|
// we already resolved the cache entry so log the error here
|
|
|
|
if (resolved) {
|
|
|
|
console.error(err)
|
|
|
|
} else {
|
|
|
|
rejecter(err as Error)
|
|
|
|
}
|
Replace `withCoalescedInvoke` with `ResponseCache` (#26997)
By itself, `withCoalescedInvoke` with a separate `this.incrementalCache.set(...)` isn't really suitable for streaming responses. Since streaming is asynchronous, updating the cache separately introduces a gap where another origin request for the same resource could be made.
This could potentially be addressed by moving the cache update, but then `IncrementalCache` itself would need to be made to support streaming, in addition to the many other responsibilities it has. In this case, it seemed best to just use composition to add another caching layer in front of it, which is a familiar and understandable concept. Eventually, we might want to move this cache to the HTTP layer, which will also be simpler with this change.
As an added bonus, `renderToResponseWithComponents` becomes significantly simpler, and we delete some duplication.
2021-07-12 21:47:39 +02:00
|
|
|
} finally {
|
|
|
|
if (key) {
|
|
|
|
this.pendingResponses.delete(key)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})()
|
|
|
|
return promise
|
|
|
|
}
|
|
|
|
}
|