Skip to content

Commit 0090784

Browse files
committed
Track async function's last await
An async function's last piece of code doesn't have any way to trace it back to the last await it was waiting for inside the body. Instead we guess this from whatever executed last.
1 parent 35719f2 commit 0090784

File tree

2 files changed

+35
-6
lines changed

2 files changed

+35
-6
lines changed

packages/react-server/src/ReactFlightAsyncSequence.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export type PromiseNode = {
3838
start: number, // start time when the Promise was created
3939
end: number, // end time when the Promise was resolved.
4040
awaited: null | AsyncSequence, // the thing that ended up resolving this promise
41-
previous: null, // where we created the promise is not interesting since creating it doesn't mean waiting.
41+
previous: null | AsyncSequence, // represents what the last return of an async function depended on before returning
4242
};
4343

4444
export type AwaitNode = {

packages/react-server/src/ReactFlightServerConfigDebugNode.js

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ import {enableAsyncDebugInfo} from 'shared/ReactFeatureFlags';
3030
const pendingOperations: Map<number, AsyncSequence> =
3131
__DEV__ && enableAsyncDebugInfo ? new Map() : (null: any);
3232

33+
// Keep the last resolved await as a workaround for async functions missing data.
34+
let lastRanAwait: null | AwaitNode = null;
35+
3336
function resolvePromiseOrAwaitNode(
3437
unresolvedNode: UnresolvedAwaitNode | UnresolvedPromiseNode,
3538
endTime: number,
@@ -152,23 +155,49 @@ export function initAsyncDebugInfo(): void {
152155
if (node !== undefined) {
153156
switch (node.tag) {
154157
case IO_NODE: {
158+
lastRanAwait = null;
155159
// Log the end time when we resolved the I/O. This can happen
156160
// more than once if it's a recurring resource like a connection.
157161
const ioNode: IONode = (node: any);
158162
ioNode.end = performance.now();
159163
break;
160164
}
161-
case UNRESOLVED_AWAIT_NODE:
162-
case UNRESOLVED_PROMISE_NODE: {
165+
case UNRESOLVED_AWAIT_NODE: {
163166
// If we begin before we resolve, that means that this is actually already resolved but
164167
// the promiseResolve hook is called at the end of the execution. So we track the time
165-
// in the beginning instead.
166-
resolvePromiseOrAwaitNode(node, performance.now());
168+
// in the before call instead.
169+
// $FlowFixMe
170+
lastRanAwait = resolvePromiseOrAwaitNode(node, performance.now());
167171
break;
168172
}
173+
case AWAIT_NODE: {
174+
lastRanAwait = node;
175+
break;
176+
}
177+
case UNRESOLVED_PROMISE_NODE: {
178+
// We typically don't expected Promises to have an execution scope since only the awaits
179+
// have a then() callback. However, this can happen for native async functions. The last
180+
// piece of code that executes the return after the last await has the execution context
181+
// of the Promise.
182+
const resolvedNode = resolvePromiseOrAwaitNode(
183+
node,
184+
performance.now(),
185+
);
186+
// We are missing information about what this was unblocked by but we can guess that it
187+
// was whatever await we ran last since this will continue in a microtask after that.
188+
// This is not perfect because there could potentially be other microtasks getting in
189+
// between.
190+
resolvedNode.previous = lastRanAwait;
191+
lastRanAwait = null;
192+
break;
193+
}
194+
default: {
195+
lastRanAwait = null;
196+
}
169197
}
170198
}
171199
},
200+
172201
promiseResolve(asyncId: number): void {
173202
const node = pendingOperations.get(asyncId);
174203
if (node !== undefined) {
@@ -181,8 +210,8 @@ export function initAsyncDebugInfo(): void {
181210
}
182211
case AWAIT_NODE:
183212
case PROMISE_NODE: {
213+
// We already resolved this in the begin hook.
184214
resolvedNode = node;
185-
// We already resolved this in the begin phase.
186215
break;
187216
}
188217
default:

0 commit comments

Comments
 (0)