| | import { InvariantError } from '../../shared/lib/invariant-error' |
| | import { createAtomicTimerGroup } from './app-render-scheduling' |
| | import { |
| | DANGEROUSLY_runPendingImmediatesAfterCurrentTask, |
| | expectNoPendingImmediates, |
| | } from '../node-environment-extensions/fast-set-immediate.external' |
| |
|
| | |
| | |
| | |
| | |
| | export function prerenderAndAbortInSequentialTasks<R>( |
| | prerender: () => Promise<R>, |
| | abort: () => void |
| | ): Promise<R> { |
| | if (process.env.NEXT_RUNTIME === 'edge') { |
| | throw new InvariantError( |
| | '`prerenderAndAbortInSequentialTasks` should not be called in edge runtime.' |
| | ) |
| | } else { |
| | return new Promise((resolve, reject) => { |
| | const scheduleTimeout = createAtomicTimerGroup() |
| |
|
| | let pendingResult: Promise<R> |
| | scheduleTimeout(() => { |
| | try { |
| | DANGEROUSLY_runPendingImmediatesAfterCurrentTask() |
| | pendingResult = prerender() |
| | pendingResult.catch(() => {}) |
| | } catch (err) { |
| | reject(err) |
| | } |
| | }) |
| | scheduleTimeout(() => { |
| | try { |
| | expectNoPendingImmediates() |
| | abort() |
| | resolve(pendingResult) |
| | } catch (err) { |
| | reject(err) |
| | } |
| | }) |
| | }) |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | export function prerenderAndAbortInSequentialTasksWithStages<R>( |
| | prerender: () => Promise<R>, |
| | advanceStage: () => void, |
| | abort: () => void |
| | ): Promise<R> { |
| | if (process.env.NEXT_RUNTIME === 'edge') { |
| | throw new InvariantError( |
| | '`prerenderAndAbortInSequentialTasksWithStages` should not be called in edge runtime.' |
| | ) |
| | } else { |
| | return new Promise((resolve, reject) => { |
| | const scheduleTimeout = createAtomicTimerGroup() |
| |
|
| | let pendingResult: Promise<R> |
| | scheduleTimeout(() => { |
| | try { |
| | DANGEROUSLY_runPendingImmediatesAfterCurrentTask() |
| | pendingResult = prerender() |
| | pendingResult.catch(() => {}) |
| | } catch (err) { |
| | reject(err) |
| | } |
| | }) |
| | scheduleTimeout(() => { |
| | try { |
| | DANGEROUSLY_runPendingImmediatesAfterCurrentTask() |
| | advanceStage() |
| | } catch (err) { |
| | reject(err) |
| | } |
| | }) |
| | scheduleTimeout(() => { |
| | try { |
| | expectNoPendingImmediates() |
| | abort() |
| | resolve(pendingResult) |
| | } catch (err) { |
| | reject(err) |
| | } |
| | }) |
| | }) |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | export class ReactServerResult { |
| | private _stream: null | ReadableStream<Uint8Array> |
| |
|
| | constructor(stream: ReadableStream<Uint8Array>) { |
| | this._stream = stream |
| | } |
| |
|
| | tee() { |
| | if (this._stream === null) { |
| | throw new Error( |
| | 'Cannot tee a ReactServerResult that has already been consumed' |
| | ) |
| | } |
| | const tee = this._stream.tee() |
| | this._stream = tee[0] |
| | return tee[1] |
| | } |
| |
|
| | consume() { |
| | if (this._stream === null) { |
| | throw new Error( |
| | 'Cannot consume a ReactServerResult that has already been consumed' |
| | ) |
| | } |
| | const stream = this._stream |
| | this._stream = null |
| | return stream |
| | } |
| | } |
| |
|
| | export type ReactServerPrerenderResolveToType = { |
| | prelude: ReadableStream<Uint8Array> |
| | } |
| |
|
| | export async function createReactServerPrerenderResult( |
| | underlying: Promise<ReactServerPrerenderResolveToType> |
| | ): Promise<ReactServerPrerenderResult> { |
| | const chunks: Array<Uint8Array> = [] |
| | const { prelude } = await underlying |
| | const reader = prelude.getReader() |
| | while (true) { |
| | const { done, value } = await reader.read() |
| | if (done) { |
| | return new ReactServerPrerenderResult(chunks) |
| | } else { |
| | chunks.push(value) |
| | } |
| | } |
| | } |
| |
|
| | export async function createReactServerPrerenderResultFromRender( |
| | underlying: ReadableStream<Uint8Array> |
| | ): Promise<ReactServerPrerenderResult> { |
| | const chunks: Array<Uint8Array> = [] |
| | const reader = underlying.getReader() |
| | while (true) { |
| | const { done, value } = await reader.read() |
| | if (done) { |
| | break |
| | } else { |
| | chunks.push(value) |
| | } |
| | } |
| | return new ReactServerPrerenderResult(chunks) |
| | } |
| | export class ReactServerPrerenderResult { |
| | private _chunks: null | Array<Uint8Array> |
| |
|
| | private assertChunks(expression: string): Array<Uint8Array> { |
| | if (this._chunks === null) { |
| | throw new InvariantError( |
| | `Cannot \`${expression}\` on a ReactServerPrerenderResult that has already been consumed.` |
| | ) |
| | } |
| | return this._chunks |
| | } |
| |
|
| | private consumeChunks(expression: string): Array<Uint8Array> { |
| | const chunks = this.assertChunks(expression) |
| | this.consume() |
| | return chunks |
| | } |
| |
|
| | consume(): void { |
| | this._chunks = null |
| | } |
| |
|
| | constructor(chunks: Array<Uint8Array>) { |
| | this._chunks = chunks |
| | } |
| |
|
| | asUnclosingStream(): ReadableStream<Uint8Array> { |
| | const chunks = this.assertChunks('asUnclosingStream()') |
| | return createUnclosingStream(chunks) |
| | } |
| |
|
| | consumeAsUnclosingStream(): ReadableStream<Uint8Array> { |
| | const chunks = this.consumeChunks('consumeAsUnclosingStream()') |
| | return createUnclosingStream(chunks) |
| | } |
| |
|
| | asStream(): ReadableStream<Uint8Array> { |
| | const chunks = this.assertChunks('asStream()') |
| | return createClosingStream(chunks) |
| | } |
| |
|
| | consumeAsStream(): ReadableStream<Uint8Array> { |
| | const chunks = this.consumeChunks('consumeAsStream()') |
| | return createClosingStream(chunks) |
| | } |
| | } |
| |
|
| | function createUnclosingStream( |
| | chunks: Array<Uint8Array> |
| | ): ReadableStream<Uint8Array> { |
| | let i = 0 |
| | return new ReadableStream({ |
| | async pull(controller) { |
| | if (i < chunks.length) { |
| | controller.enqueue(chunks[i++]) |
| | } |
| | |
| | |
| | |
| | }, |
| | }) |
| | } |
| |
|
| | function createClosingStream( |
| | chunks: Array<Uint8Array> |
| | ): ReadableStream<Uint8Array> { |
| | let i = 0 |
| | return new ReadableStream({ |
| | async pull(controller) { |
| | if (i < chunks.length) { |
| | controller.enqueue(chunks[i++]) |
| | } else { |
| | controller.close() |
| | } |
| | }, |
| | }) |
| | } |
| |
|
| | export async function processPrelude( |
| | unprocessedPrelude: ReadableStream<Uint8Array> |
| | ) { |
| | const [prelude, peek] = unprocessedPrelude.tee() |
| |
|
| | const reader = peek.getReader() |
| | const firstResult = await reader.read() |
| | reader.cancel() |
| |
|
| | const preludeIsEmpty = firstResult.done === true |
| |
|
| | return { prelude, preludeIsEmpty } |
| | } |
| |
|