Skip to content

Commit 45bc3c9

Browse files
authored
[Flight] Reduce risk of maximum call stack exceeded when emitting async sequence (facebook#35159)
1 parent fb2177c commit 45bc3c9

File tree

1 file changed

+23
-8
lines changed

1 file changed

+23
-8
lines changed

packages/react-server/src/ReactFlightServer.js

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2347,7 +2347,8 @@ function visitAsyncNodeImpl(
23472347
// The technique for debugging the effects of uncached data on the render is to simply uncache it.
23482348
return null;
23492349
}
2350-
let previousIONode = null;
2350+
2351+
let previousIONode: void | null | PromiseNode | IONode = null;
23512352
// First visit anything that blocked this sequence to start in the first place.
23522353
if (node.previous !== null) {
23532354
previousIONode = visitAsyncNode(
@@ -2363,12 +2364,20 @@ function visitAsyncNodeImpl(
23632364
return undefined;
23642365
}
23652366
}
2367+
2368+
// `found` represents the return value of the following switch statement.
2369+
// We can't use multiple `return` statements in the switch statement
2370+
// since that prevents Closure compiler from inlining `visitAsyncImpl`
2371+
// thus doubling the call stack size.
2372+
let found: void | null | PromiseNode | IONode;
23662373
switch (node.tag) {
23672374
case IO_NODE: {
2368-
return node;
2375+
found = node;
2376+
break;
23692377
}
23702378
case UNRESOLVED_PROMISE_NODE: {
2371-
return previousIONode;
2379+
found = previousIONode;
2380+
break;
23722381
}
23732382
case PROMISE_NODE: {
23742383
const awaited = node.awaited;
@@ -2379,7 +2388,8 @@ function visitAsyncNodeImpl(
23792388
if (ioNode === undefined) {
23802389
// Undefined is used as a signal that we found a suitable aborted node and we don't have to find
23812390
// further aborted nodes.
2382-
return undefined;
2391+
found = undefined;
2392+
break;
23832393
} else if (ioNode !== null) {
23842394
// This Promise was blocked on I/O. That's a signal that this Promise is interesting to log.
23852395
// We don't log it yet though. We return it to be logged by the point where it's awaited.
@@ -2436,10 +2446,12 @@ function visitAsyncNodeImpl(
24362446
forwardDebugInfo(request, task, debugInfo);
24372447
}
24382448
}
2439-
return match;
2449+
found = match;
2450+
break;
24402451
}
24412452
case UNRESOLVED_AWAIT_NODE: {
2442-
return previousIONode;
2453+
found = previousIONode;
2454+
break;
24432455
}
24442456
case AWAIT_NODE: {
24452457
const awaited = node.awaited;
@@ -2449,7 +2461,8 @@ function visitAsyncNodeImpl(
24492461
if (ioNode === undefined) {
24502462
// Undefined is used as a signal that we found a suitable aborted node and we don't have to find
24512463
// further aborted nodes.
2452-
return undefined;
2464+
found = undefined;
2465+
break;
24532466
} else if (ioNode !== null) {
24542467
const startTime: number = node.start;
24552468
const endTime: number = node.end;
@@ -2545,13 +2558,15 @@ function visitAsyncNodeImpl(
25452558
forwardDebugInfo(request, task, debugInfo);
25462559
}
25472560
}
2548-
return match;
2561+
found = match;
2562+
break;
25492563
}
25502564
default: {
25512565
// eslint-disable-next-line react-internal/prod-error-codes
25522566
throw new Error('Unknown AsyncSequence tag. This is a bug in React.');
25532567
}
25542568
}
2569+
return found;
25552570
}
25562571

25572572
function emitAsyncSequence(

0 commit comments

Comments
 (0)