mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[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:
+58
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user