Skip to content

Commit 3a713c5

Browse files
committed
Reject any new Chunks not yet discovered at the time of reportGlobalError
1 parent 6a4b46c commit 3a713c5

File tree

2 files changed

+45
-0
lines changed

2 files changed

+45
-0
lines changed

packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReplyEdge-test.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,4 +245,31 @@ describe('ReactFlightDOMReplyEdge', () => {
245245
),
246246
);
247247
});
248+
249+
it('should abort when parsing an incomplete payload', async () => {
250+
const infinitePromise = new Promise(() => {});
251+
const controller = new AbortController();
252+
const promiseForResult = ReactServerDOMClient.encodeReply(
253+
{promise: infinitePromise},
254+
{
255+
signal: controller.signal,
256+
},
257+
);
258+
controller.abort();
259+
const body = await promiseForResult;
260+
261+
const decoded = await ReactServerDOMServer.decodeReply(
262+
body,
263+
webpackServerMap,
264+
);
265+
266+
let error = null;
267+
try {
268+
await decoded.promise;
269+
} catch (x) {
270+
error = x;
271+
}
272+
expect(error).not.toBe(null);
273+
expect(error.message).toBe('Connection closed.');
274+
});
248275
});

packages/react-server/src/ReactFlightReplyServer.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,8 @@ export type Response = {
169169
_prefix: string,
170170
_formData: FormData,
171171
_chunks: Map<number, SomeChunk<any>>,
172+
_closed: boolean,
173+
_closedReason: mixed,
172174
_temporaryReferences: void | TemporaryReferenceSet,
173175
};
174176

@@ -255,6 +257,14 @@ function createResolvedModelChunk<T>(
255257
return new Chunk(RESOLVED_MODEL, value, id, response);
256258
}
257259

260+
function createErroredChunk<T>(
261+
response: Response,
262+
reason: mixed,
263+
): ErroredChunk<T> {
264+
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
265+
return new Chunk(ERRORED, null, reason, response);
266+
}
267+
258268
function resolveModelChunk<T>(
259269
chunk: SomeChunk<T>,
260270
value: string,
@@ -493,6 +503,8 @@ function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void {
493503
// Report that any missing chunks in the model is now going to throw this
494504
// error upon read. Also notify any pending promises.
495505
export function reportGlobalError(response: Response, error: Error): void {
506+
response._closed = true;
507+
response._closedReason = error;
496508
response._chunks.forEach(chunk => {
497509
// If this chunk was already resolved or errored, it won't
498510
// trigger an error but if it wasn't then we need to
@@ -514,6 +526,10 @@ function getChunk(response: Response, id: number): SomeChunk<any> {
514526
if (backingEntry != null) {
515527
// We assume that this is a string entry for now.
516528
chunk = createResolvedModelChunk(response, (backingEntry: any), id);
529+
} else if (response._closed) {
530+
// We have already errored the response and we're not going to get
531+
// anything more streaming in so this will immediately error.
532+
chunk = createErroredChunk(response, response._closedReason);
517533
} else {
518534
// We're still waiting on this entry to stream in.
519535
chunk = createPendingChunk(response);
@@ -1082,6 +1098,8 @@ export function createResponse(
10821098
_prefix: formFieldPrefix,
10831099
_formData: backingFormData,
10841100
_chunks: chunks,
1101+
_closed: false,
1102+
_closedReason: null,
10851103
_temporaryReferences: temporaryReferences,
10861104
};
10871105
return response;

0 commit comments

Comments
 (0)