@@ -194,6 +194,49 @@ function devirtualizeURL(url: string): string {
194194 return url;
195195}
196196
197+ function isPromiseCreationInternal ( url : string , functionName : string ) : boolean {
198+ // Various internals of the JS VM can create Promises but the call frame of the
199+ // internals are not very interesting for our purposes so we need to skip those.
200+ if ( url === 'node:internal/async_hooks' ) {
201+ // Ignore the stack frames from the async hooks themselves.
202+ return true ;
203+ }
204+ if (url !== '') {
205+ return false ;
206+ }
207+ switch (functionName) {
208+ case 'new Promise' :
209+ case 'Function.withResolvers' :
210+ case 'Function.reject' :
211+ case 'Function.resolve' :
212+ case 'Function.all' :
213+ case 'Function.allSettled' :
214+ case 'Function.race' :
215+ case 'Function.try' :
216+ return true ;
217+ default :
218+ return false ;
219+ }
220+ }
221+
222+ function stripLeadingPromiseCreationFrames (
223+ stack : ReactStackTrace ,
224+ ) : ReactStackTrace {
225+ for ( let i = 0 ; i < stack . length ; i ++ ) {
226+ const callsite = stack [ i ] ;
227+ const functionName = callsite [ 0 ] ;
228+ const url = callsite [ 1 ] ;
229+ if ( ! isPromiseCreationInternal ( url , functionName ) ) {
230+ if ( i > 0 ) {
231+ return stack . slice ( i ) ;
232+ } else {
233+ return stack ;
234+ }
235+ }
236+ }
237+ return [];
238+ }
239+
197240function findCalledFunctionNameFromStackTrace (
198241 request : Request ,
199242 stack : ReactStackTrace ,
@@ -207,11 +250,7 @@ function findCalledFunctionNameFromStackTrace(
207250 const url = devirtualizeURL ( callsite [ 1 ] ) ;
208251 const lineNumber = callsite [ 2 ] ;
209252 const columnNumber = callsite [ 3 ] ;
210- if ( functionName === 'new Promise' ) {
211- // Ignore Promise constructors.
212- } else if ( url === 'node:internal/async_hooks' ) {
213- // Ignore the stack frames from the async hooks themselves.
214- } else if ( filterStackFrame ( url , functionName , lineNumber , columnNumber ) ) {
253+ if ( filterStackFrame ( url , functionName , lineNumber , columnNumber ) ) {
215254 if ( bestMatch === '' ) {
216255 // If we had no good stack frames for internal calls, just use the last
217256 // first party function name.
@@ -275,13 +314,44 @@ function hasUnfilteredFrame(request: Request, stack: ReactStackTrace): boolean {
275314 return false ;
276315}
277316
317+ function isPromiseAwaitInternal(url: string, functionName: string): boolean {
318+ // Various internals of the JS VM can await internally on a Promise. If those are at
319+ // the top of the stack then we don't want to consider them as internal frames. The
320+ // true "await" conceptually is the thing that called the helper.
321+ // Ideally we'd also include common third party helpers for this.
322+ if ( url === 'node:internal/async_hooks' ) {
323+ // Ignore the stack frames from the async hooks themselves.
324+ return true ;
325+ }
326+ if (url !== '') {
327+ return false ;
328+ }
329+ switch (functionName) {
330+ case 'Promise.then' :
331+ case 'Promise.catch' :
332+ case 'Promise.finally' :
333+ case 'Function.reject' :
334+ case 'Function.resolve' :
335+ case 'Function.all' :
336+ case 'Function.allSettled' :
337+ case 'Function.race' :
338+ case 'Function.try' :
339+ return true ;
340+ default :
341+ return false ;
342+ }
343+ }
344+
278345export function isAwaitInUserspace (
279346 request : Request ,
280347 stack : ReactStackTrace ,
281348) : boolean {
282349 let firstFrame = 0 ;
283- while ( stack . length > firstFrame && stack [ firstFrame ] [ 0 ] === 'Promise.then' ) {
284- // Skip Promise.then frame itself.
350+ while (
351+ stack . length > firstFrame &&
352+ isPromiseAwaitInternal ( stack [ firstFrame ] [ 1 ] , stack [ firstFrame ] [ 0 ] )
353+ ) {
354+ // Skip the internal frame that awaits itself.
285355 firstFrame ++ ;
286356 }
287357 if (stack.length > firstFrame ) {
@@ -4213,7 +4283,8 @@ function serializeIONode(
42134283 let stack = null ;
42144284 let name = '';
42154285 if ( ioNode . stack !== null ) {
4216- const fullStack = ioNode . stack ;
4286+ // The stack can contain some leading internal frames for the construction of the promise that we skip.
4287+ const fullStack = stripLeadingPromiseCreationFrames ( ioNode . stack ) ;
42174288 stack = filterStackTrace ( request , fullStack ) ;
42184289 name = findCalledFunctionNameFromStackTrace ( request , fullStack ) ;
42194290 // The name can include the object that this was called on but sometimes that's
0 commit comments