626955d61c
Something [between `11.0.2-canary.5` and `11.0.2-canary.6`](https://github.com/vercel/next.js/compare/v11.0.2-canary.5...v11.0.2-canary.6) changed the behavior that logged any runtime errors in `getStaticProps` to stderr. This is only observable if `getStaticProps` has a `revalidate` value, and the build did not fail. The error has to happen in a subsequent revalidation step. This PR reverts the change and fixes #30375. ## Bug - [x] Related issues linked using `fixes #number` - [x] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes by running `yarn lint` Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com>
125 lines
3.6 KiB
TypeScript
125 lines
3.6 KiB
TypeScript
import { IncrementalCache } from './incremental-cache'
|
|
import RenderResult from './render-result'
|
|
|
|
interface CachedRedirectValue {
|
|
kind: 'REDIRECT'
|
|
props: Object
|
|
}
|
|
|
|
interface CachedPageValue {
|
|
kind: 'PAGE'
|
|
html: RenderResult
|
|
pageData: Object
|
|
}
|
|
|
|
export type ResponseCacheValue = CachedRedirectValue | CachedPageValue
|
|
|
|
export type ResponseCacheEntry = {
|
|
revalidate?: number | false
|
|
value: ResponseCacheValue | null
|
|
}
|
|
|
|
type ResponseGenerator = (
|
|
hasResolved: boolean
|
|
) => Promise<ResponseCacheEntry | null>
|
|
|
|
export default class ResponseCache {
|
|
incrementalCache: IncrementalCache
|
|
pendingResponses: Map<string, Promise<ResponseCacheEntry | null>>
|
|
|
|
constructor(incrementalCache: IncrementalCache) {
|
|
this.incrementalCache = incrementalCache
|
|
this.pendingResponses = new Map()
|
|
}
|
|
|
|
public get(
|
|
key: string | null,
|
|
responseGenerator: ResponseGenerator
|
|
): Promise<ResponseCacheEntry | null> {
|
|
const pendingResponse = key ? this.pendingResponses.get(key) : null
|
|
if (pendingResponse) {
|
|
return pendingResponse
|
|
}
|
|
|
|
let resolver: (cacheEntry: ResponseCacheEntry | null) => void = () => {}
|
|
let rejecter: (error: Error) => void = () => {}
|
|
const promise: Promise<ResponseCacheEntry | null> = new Promise(
|
|
(resolve, reject) => {
|
|
resolver = resolve
|
|
rejecter = reject
|
|
}
|
|
)
|
|
if (key) {
|
|
this.pendingResponses.set(key, promise)
|
|
}
|
|
|
|
let resolved = false
|
|
const resolve = (cacheEntry: ResponseCacheEntry | null) => {
|
|
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)
|
|
}
|
|
}
|
|
|
|
// 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 () => {
|
|
try {
|
|
const cachedResponse = key ? await this.incrementalCache.get(key) : null
|
|
if (cachedResponse) {
|
|
resolve({
|
|
revalidate: cachedResponse.curRevalidate,
|
|
value:
|
|
cachedResponse.value?.kind === 'PAGE'
|
|
? {
|
|
kind: 'PAGE',
|
|
html: RenderResult.fromStatic(cachedResponse.value.html),
|
|
pageData: cachedResponse.value.pageData,
|
|
}
|
|
: cachedResponse.value,
|
|
})
|
|
if (!cachedResponse.isStale) {
|
|
// The cached value is still valid, so we don't need
|
|
// to update it yet.
|
|
return
|
|
}
|
|
}
|
|
|
|
const cacheEntry = await responseGenerator(resolved)
|
|
resolve(cacheEntry)
|
|
|
|
if (key && cacheEntry && typeof cacheEntry.revalidate !== 'undefined') {
|
|
await this.incrementalCache.set(
|
|
key,
|
|
cacheEntry.value?.kind === 'PAGE'
|
|
? {
|
|
kind: 'PAGE',
|
|
html: cacheEntry.value.html.toUnchunkedString(),
|
|
pageData: cacheEntry.value.pageData,
|
|
}
|
|
: cacheEntry.value,
|
|
cacheEntry.revalidate
|
|
)
|
|
}
|
|
} catch (err) {
|
|
// 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)
|
|
}
|
|
} finally {
|
|
if (key) {
|
|
this.pendingResponses.delete(key)
|
|
}
|
|
}
|
|
})()
|
|
return promise
|
|
}
|
|
}
|