diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js index 0a453e0c07865..cc4f0b5331b72 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js @@ -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( + + + , + 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( +
+ Shell: +
, + ); + }); + // 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); + }); }); diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index 5da20fcddae45..479a4a746d5e2 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -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(); }