[Flight] Aborting with a postpone instance as a reason should postpone remaining holes (#27576)

This lets you abort with postponing semantics.
This commit is contained in:
Sebastian Markbåge
2023-10-24 13:41:28 -07:00
committed by GitHub
parent b8e47d988e
commit 960ed6ea43
2 changed files with 77 additions and 7 deletions
@@ -1309,4 +1309,62 @@ describe('ReactFlightDOMBrowser', () => {
'The render was aborted by the server without a reason.',
]);
});
// @gate enablePostpone
it('postpones when abort passes a postpone signal', async () => {
const infinitePromise = new Promise(() => {});
function Server() {
return infinitePromise;
}
let postponed = null;
let error = null;
const controller = new AbortController();
const stream = ReactServerDOMServer.renderToReadableStream(
<Suspense fallback="Loading...">
<Server />
</Suspense>,
null,
{
onError(x) {
error = x;
},
onPostpone(reason) {
postponed = reason;
},
signal: controller.signal,
},
);
try {
React.unstable_postpone('testing postpone');
} catch (reason) {
controller.abort(reason);
}
const response = ReactServerDOMClient.createFromReadableStream(stream);
function Client() {
return use(response);
}
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(async () => {
root.render(
<div>
Shell: <Client />
</div>,
);
});
// We should have reserved the shell already. Which means that the Server
// Component should've been a lazy component.
expect(container.innerHTML).toContain('Shell:');
expect(container.innerHTML).toContain('Loading...');
expect(container.innerHTML).not.toContain('Not shown');
expect(postponed).toBe('testing postpone');
expect(error).toBe(null);
});
});
+19 -7
View File
@@ -1790,15 +1790,27 @@ export function abort(request: Request, reason: mixed): void {
if (abortableTasks.size > 0) {
// We have tasks to abort. We'll emit one error row and then emit a reference
// to that row from every row that's still remaining.
const error =
reason === undefined
? new Error('The render was aborted by the server without a reason.')
: reason;
const digest = logRecoverableError(request, error);
request.pendingChunks++;
const errorId = request.nextChunkId++;
emitErrorChunk(request, errorId, digest, error);
if (
enablePostpone &&
typeof reason === 'object' &&
reason !== null &&
(reason: any).$$typeof === REACT_POSTPONE_TYPE
) {
const postponeInstance: Postpone = (reason: any);
logPostpone(request, postponeInstance.message);
emitPostponeChunk(request, errorId, postponeInstance);
} else {
const error =
reason === undefined
? new Error(
'The render was aborted by the server without a reason.',
)
: reason;
const digest = logRecoverableError(request, error);
emitErrorChunk(request, errorId, digest, error);
}
abortableTasks.forEach(task => abortTask(task, request, errorId));
abortableTasks.clear();
}