mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 00:20:04 +01:00
[Flight] Skip the stack frame of built-in wrappers that create or await Promises (#33798)
We already do this with `"new Promise"` and `"Promise.then"`. There are also many helpers that both create promises and awaits other promises inside of it like `Promise.all`. The way this is filtered is different from just filtering out all anonymous stacks since they're used to determine where the boundary is between ignore listed and user space. Ideally we'd cover more wrappers that are internal to Promise libraries.
This commit is contained in:
parent
9fec565a9b
commit
da7487b681
87
packages/react-server/src/ReactFlightServer.js
vendored
87
packages/react-server/src/ReactFlightServer.js
vendored
|
|
@ -194,6 +194,49 @@ function devirtualizeURL(url: string): string {
|
|||
return url;
|
||||
}
|
||||
|
||||
function isPromiseCreationInternal(url: string, functionName: string): boolean {
|
||||
// Various internals of the JS VM can create Promises but the call frame of the
|
||||
// internals are not very interesting for our purposes so we need to skip those.
|
||||
if (url === 'node:internal/async_hooks') {
|
||||
// Ignore the stack frames from the async hooks themselves.
|
||||
return true;
|
||||
}
|
||||
if (url !== '') {
|
||||
return false;
|
||||
}
|
||||
switch (functionName) {
|
||||
case 'new Promise':
|
||||
case 'Function.withResolvers':
|
||||
case 'Function.reject':
|
||||
case 'Function.resolve':
|
||||
case 'Function.all':
|
||||
case 'Function.allSettled':
|
||||
case 'Function.race':
|
||||
case 'Function.try':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function stripLeadingPromiseCreationFrames(
|
||||
stack: ReactStackTrace,
|
||||
): ReactStackTrace {
|
||||
for (let i = 0; i < stack.length; i++) {
|
||||
const callsite = stack[i];
|
||||
const functionName = callsite[0];
|
||||
const url = callsite[1];
|
||||
if (!isPromiseCreationInternal(url, functionName)) {
|
||||
if (i > 0) {
|
||||
return stack.slice(i);
|
||||
} else {
|
||||
return stack;
|
||||
}
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function findCalledFunctionNameFromStackTrace(
|
||||
request: Request,
|
||||
stack: ReactStackTrace,
|
||||
|
|
@ -207,11 +250,7 @@ function findCalledFunctionNameFromStackTrace(
|
|||
const url = devirtualizeURL(callsite[1]);
|
||||
const lineNumber = callsite[2];
|
||||
const columnNumber = callsite[3];
|
||||
if (functionName === 'new Promise') {
|
||||
// Ignore Promise constructors.
|
||||
} else if (url === 'node:internal/async_hooks') {
|
||||
// Ignore the stack frames from the async hooks themselves.
|
||||
} else if (filterStackFrame(url, functionName, lineNumber, columnNumber)) {
|
||||
if (filterStackFrame(url, functionName, lineNumber, columnNumber)) {
|
||||
if (bestMatch === '') {
|
||||
// If we had no good stack frames for internal calls, just use the last
|
||||
// first party function name.
|
||||
|
|
@ -275,13 +314,44 @@ function hasUnfilteredFrame(request: Request, stack: ReactStackTrace): boolean {
|
|||
return false;
|
||||
}
|
||||
|
||||
function isPromiseAwaitInternal(url: string, functionName: string): boolean {
|
||||
// Various internals of the JS VM can await internally on a Promise. If those are at
|
||||
// the top of the stack then we don't want to consider them as internal frames. The
|
||||
// true "await" conceptually is the thing that called the helper.
|
||||
// Ideally we'd also include common third party helpers for this.
|
||||
if (url === 'node:internal/async_hooks') {
|
||||
// Ignore the stack frames from the async hooks themselves.
|
||||
return true;
|
||||
}
|
||||
if (url !== '') {
|
||||
return false;
|
||||
}
|
||||
switch (functionName) {
|
||||
case 'Promise.then':
|
||||
case 'Promise.catch':
|
||||
case 'Promise.finally':
|
||||
case 'Function.reject':
|
||||
case 'Function.resolve':
|
||||
case 'Function.all':
|
||||
case 'Function.allSettled':
|
||||
case 'Function.race':
|
||||
case 'Function.try':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function isAwaitInUserspace(
|
||||
request: Request,
|
||||
stack: ReactStackTrace,
|
||||
): boolean {
|
||||
let firstFrame = 0;
|
||||
while (stack.length > firstFrame && stack[firstFrame][0] === 'Promise.then') {
|
||||
// Skip Promise.then frame itself.
|
||||
while (
|
||||
stack.length > firstFrame &&
|
||||
isPromiseAwaitInternal(stack[firstFrame][1], stack[firstFrame][0])
|
||||
) {
|
||||
// Skip the internal frame that awaits itself.
|
||||
firstFrame++;
|
||||
}
|
||||
if (stack.length > firstFrame) {
|
||||
|
|
@ -4213,7 +4283,8 @@ function serializeIONode(
|
|||
let stack = null;
|
||||
let name = '';
|
||||
if (ioNode.stack !== null) {
|
||||
const fullStack = ioNode.stack;
|
||||
// The stack can contain some leading internal frames for the construction of the promise that we skip.
|
||||
const fullStack = stripLeadingPromiseCreationFrames(ioNode.stack);
|
||||
stack = filterStackTrace(request, fullStack);
|
||||
name = findCalledFunctionNameFromStackTrace(request, fullStack);
|
||||
// The name can include the object that this was called on but sometimes that's
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user