| | import type { NonStaticRenderStage } from './app-render/staged-rendering' |
| | import type { RequestStore } from './app-render/work-unit-async-storage.external' |
| |
|
| | export function isHangingPromiseRejectionError( |
| | err: unknown |
| | ): err is HangingPromiseRejectionError { |
| | if (typeof err !== 'object' || err === null || !('digest' in err)) { |
| | return false |
| | } |
| |
|
| | return err.digest === HANGING_PROMISE_REJECTION |
| | } |
| |
|
| | const HANGING_PROMISE_REJECTION = 'HANGING_PROMISE_REJECTION' |
| |
|
| | class HangingPromiseRejectionError extends Error { |
| | public readonly digest = HANGING_PROMISE_REJECTION |
| |
|
| | constructor( |
| | public readonly route: string, |
| | public readonly expression: string |
| | ) { |
| | super( |
| | `During prerendering, ${expression} rejects when the prerender is complete. Typically these errors are handled by React but if you move ${expression} to a different context by using \`setTimeout\`, \`after\`, or similar functions you may observe this error and you should handle it in that context. This occurred at route "${route}".` |
| | ) |
| | } |
| | } |
| |
|
| | type AbortListeners = Array<(err: unknown) => void> |
| | const abortListenersBySignal = new WeakMap<AbortSignal, AbortListeners>() |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | export function makeHangingPromise<T>( |
| | signal: AbortSignal, |
| | route: string, |
| | expression: string |
| | ): Promise<T> { |
| | if (signal.aborted) { |
| | return Promise.reject(new HangingPromiseRejectionError(route, expression)) |
| | } else { |
| | const hangingPromise = new Promise<T>((_, reject) => { |
| | const boundRejection = reject.bind( |
| | null, |
| | new HangingPromiseRejectionError(route, expression) |
| | ) |
| | let currentListeners = abortListenersBySignal.get(signal) |
| | if (currentListeners) { |
| | currentListeners.push(boundRejection) |
| | } else { |
| | const listeners = [boundRejection] |
| | abortListenersBySignal.set(signal, listeners) |
| | signal.addEventListener( |
| | 'abort', |
| | () => { |
| | for (let i = 0; i < listeners.length; i++) { |
| | listeners[i]() |
| | } |
| | }, |
| | { once: true } |
| | ) |
| | } |
| | }) |
| | |
| | |
| | |
| | hangingPromise.catch(ignoreReject) |
| | return hangingPromise |
| | } |
| | } |
| |
|
| | function ignoreReject() {} |
| |
|
| | export function makeDevtoolsIOAwarePromise<T>( |
| | underlying: T, |
| | requestStore: RequestStore, |
| | stage: NonStaticRenderStage |
| | ): Promise<T> { |
| | if (requestStore.stagedRendering) { |
| | |
| | return requestStore.stagedRendering.delayUntilStage( |
| | stage, |
| | undefined, |
| | underlying |
| | ) |
| | } |
| | |
| | |
| | return new Promise<T>((resolve) => { |
| | |
| | setTimeout(() => { |
| | resolve(underlying) |
| | }, 0) |
| | }) |
| | } |
| |
|