Skip to content

Commit cb1e7b1

Browse files
authored
Move onCompleteAll to .allReady Promise (#24025)
* Move onCompleteAll to .allReady Promise The onCompleteAll callback can sometimes resolve before the promise that returns the stream which is tough to coordinate. A more idiomatic API for a one shot event is a Promise. That way the way you render for SEO or SSG is: const stream = await renderToReadableStream(...); await stream.readyAll; respondWith(stream); Ideally this should be a sub-class of ReadableStream but we don't yet compile these to ES6 and they'd had to be to native class to subclass a native stream. I have other ideas for overriding the .tee() method in a subclass anyway. So this is inline with that strategy. * Reject the Promise on fatal errors
1 parent 5662857 commit cb1e7b1

6 files changed

+29
-10
lines changed

packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js

+3-5
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,10 @@ describe('ReactDOMFizzServer', () => {
105105
<Wait />
106106
</Suspense>
107107
</div>,
108-
{
109-
onCompleteAll() {
110-
isComplete = true;
111-
},
112-
},
113108
);
109+
110+
stream.allReady.then(() => (isComplete = true));
111+
114112
await jest.runAllTimers();
115113
expect(isComplete).toBe(false);
116114
// Resolve the loading.

packages/react-dom/src/server/ReactDOMFizzServerBrowser.js

+19-5
Original file line numberDiff line numberDiff line change
@@ -32,23 +32,36 @@ type Options = {|
3232
bootstrapModules?: Array<string>,
3333
progressiveChunkSize?: number,
3434
signal?: AbortSignal,
35-
onCompleteAll?: () => void,
3635
onError?: (error: mixed) => void,
3736
|};
3837

38+
// TODO: Move to sub-classing ReadableStream.
39+
type ReactDOMServerReadableStream = ReadableStream & {
40+
allReady: Promise<void>,
41+
};
42+
3943
function renderToReadableStream(
4044
children: ReactNodeList,
4145
options?: Options,
42-
): Promise<ReadableStream> {
46+
): Promise<ReactDOMServerReadableStream> {
4347
return new Promise((resolve, reject) => {
48+
let onFatalError;
49+
let onCompleteAll;
50+
const allReady = new Promise((res, rej) => {
51+
onCompleteAll = res;
52+
onFatalError = rej;
53+
});
54+
4455
function onCompleteShell() {
45-
const stream = new ReadableStream({
56+
const stream: ReactDOMServerReadableStream = (new ReadableStream({
4657
type: 'bytes',
4758
pull(controller) {
4859
startFlowing(request, controller);
4960
},
5061
cancel(reason) {},
51-
});
62+
}): any);
63+
// TODO: Move to sub-classing ReadableStream.
64+
stream.allReady = allReady;
5265
resolve(stream);
5366
}
5467
function onErrorShell(error: mixed) {
@@ -66,9 +79,10 @@ function renderToReadableStream(
6679
createRootFormatContext(options ? options.namespaceURI : undefined),
6780
options ? options.progressiveChunkSize : undefined,
6881
options ? options.onError : undefined,
69-
options ? options.onCompleteAll : undefined,
82+
onCompleteAll,
7083
onCompleteShell,
7184
onErrorShell,
85+
onFatalError,
7286
);
7387
if (options && options.signal) {
7488
const signal = options.signal;

packages/react-dom/src/server/ReactDOMFizzServerNode.js

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ function createRequestImpl(children: ReactNodeList, options: void | Options) {
6565
options ? options.onCompleteAll : undefined,
6666
options ? options.onCompleteShell : undefined,
6767
options ? options.onErrorShell : undefined,
68+
undefined,
6869
);
6970
}
7071

packages/react-dom/src/server/ReactDOMLegacyServerBrowser.js

+2
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ function renderToStringImpl(
6767
onError,
6868
undefined,
6969
onCompleteShell,
70+
undefined,
71+
undefined,
7072
);
7173
startWork(request);
7274
// If anything suspended and is still pending, we'll abort it before writing.

packages/react-dom/src/server/ReactDOMLegacyServerNode.js

+1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ function renderToNodeStreamImpl(
8383
onError,
8484
onCompleteAll,
8585
undefined,
86+
undefined,
8687
);
8788
destination.request = request;
8889
startWork(request);

packages/react-server/src/ReactFizzServer.js

+3
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ export opaque type Request = {
204204
// onErrorShell is called when the shell didn't complete. That means you probably want to
205205
// emit a different response to the stream instead.
206206
onErrorShell: (error: mixed) => void,
207+
onFatalError: (error: mixed) => void,
207208
};
208209

209210
// This is a default heuristic for how to split up the HTML content into progressive
@@ -238,6 +239,7 @@ export function createRequest(
238239
onCompleteAll: void | (() => void),
239240
onCompleteShell: void | (() => void),
240241
onErrorShell: void | ((error: mixed) => void),
242+
onFatalError: void | ((error: mixed) => void),
241243
): Request {
242244
const pingedTasks = [];
243245
const abortSet: Set<Task> = new Set();
@@ -263,6 +265,7 @@ export function createRequest(
263265
onCompleteAll: onCompleteAll === undefined ? noop : onCompleteAll,
264266
onCompleteShell: onCompleteShell === undefined ? noop : onCompleteShell,
265267
onErrorShell: onErrorShell === undefined ? noop : onErrorShell,
268+
onFatalError: onFatalError === undefined ? noop : onFatalError,
266269
};
267270
// This segment represents the root fallback.
268271
const rootSegment = createPendingSegment(request, 0, null, rootFormatContext);

0 commit comments

Comments
 (0)