Skip to content

Commit b9af140

Browse files
authored
[Flight] Use the JSX as the await stack if an await is not available (facebook#33947)
If you pass a promise to a client component to be rendered `<Client promise={promise} />` then there's an internal await inside Flight. There might also be user space awaits but those awaits may already have happened before we render this component. Conceptually they were part of the parent component and not this component. It's tricky to attribute which await should be used for the stack in this case. If we can't find an await we can use the JSX callsite as the stack frame. However, we don't want to do this for simple cases like if you return a non-native Promise from a Server Component. Since that would now use the stack of the thing that rendered the Server Component which is worse than the stack of the I/O. To fix this, I update the `debugOwner`/`debugTask`/`debugStack` when we start rendering inside the Server Component. Conceptually these represent the "parent" component and is used for errors referring to the parent like when we serialize client component props the parent is the JSX of the client component. However, when we're directly inside the Server Component we don't have a callsite of the parent really. Conceptually it would be the return call of the Server Component. This might negatively affect other types of errors but I think this is ok since this feature mainly exists for the case when you enter the child JSX.
1 parent e9638c3 commit b9af140

File tree

2 files changed

+239
-184
lines changed

2 files changed

+239
-184
lines changed

packages/react-server/src/ReactFlightServer.js

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1717,6 +1717,16 @@ function renderFunctionComponent<Props>(
17171717
// Apply special cases.
17181718
result = processServerComponentReturnValue(request, task, Component, result);
17191719

1720+
if (__DEV__) {
1721+
// From this point on, the parent is the component we just rendered until we
1722+
// hit another JSX element.
1723+
task.debugOwner = componentDebugInfo;
1724+
// Unfortunately, we don't have a stack frame for this position. Conceptually
1725+
// it would be the location of the `return` inside component that just rendered.
1726+
task.debugStack = null;
1727+
task.debugTask = null;
1728+
}
1729+
17201730
// Track this element's key on the Server Component on the keyPath context..
17211731
const prevKeyPath = task.keyPath;
17221732
const prevImplicitSlot = task.implicitSlot;
@@ -2416,13 +2426,32 @@ function emitAsyncSequence(
24162426
env: env,
24172427
};
24182428
if (__DEV__) {
2419-
if (owner != null) {
2420-
// $FlowFixMe[cannot-write]
2421-
debugInfo.owner = owner;
2422-
}
2423-
if (stack != null) {
2424-
// $FlowFixMe[cannot-write]
2425-
debugInfo.stack = filterStackTrace(request, parseStackTrace(stack, 1));
2429+
if (owner === null && stack === null) {
2430+
// We have no location for the await. We can use the JSX callsite of the parent
2431+
// as the await if this was just passed as a prop.
2432+
if (task.debugOwner !== null) {
2433+
// $FlowFixMe[cannot-write]
2434+
debugInfo.owner = task.debugOwner;
2435+
}
2436+
if (task.debugStack !== null) {
2437+
// $FlowFixMe[cannot-write]
2438+
debugInfo.stack = filterStackTrace(
2439+
request,
2440+
parseStackTrace(task.debugStack, 1),
2441+
);
2442+
}
2443+
} else {
2444+
if (owner != null) {
2445+
// $FlowFixMe[cannot-write]
2446+
debugInfo.owner = owner;
2447+
}
2448+
if (stack != null) {
2449+
// $FlowFixMe[cannot-write]
2450+
debugInfo.stack = filterStackTrace(
2451+
request,
2452+
parseStackTrace(stack, 1),
2453+
);
2454+
}
24262455
}
24272456
}
24282457
// We don't have a start time for this await but in case there was no start time emitted

0 commit comments

Comments
 (0)