@@ -194,6 +194,49 @@ function devirtualizeURL(url: string): string {
194
194
return url;
195
195
}
196
196
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
+
197
240
function findCalledFunctionNameFromStackTrace (
198
241
request : Request ,
199
242
stack : ReactStackTrace ,
@@ -207,11 +250,7 @@ function findCalledFunctionNameFromStackTrace(
207
250
const url = devirtualizeURL ( callsite [ 1 ] ) ;
208
251
const lineNumber = callsite [ 2 ] ;
209
252
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 ) ) {
215
254
if ( bestMatch === '' ) {
216
255
// If we had no good stack frames for internal calls, just use the last
217
256
// first party function name.
@@ -275,13 +314,44 @@ function hasUnfilteredFrame(request: Request, stack: ReactStackTrace): boolean {
275
314
return false ;
276
315
}
277
316
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
+
278
345
export function isAwaitInUserspace (
279
346
request : Request ,
280
347
stack : ReactStackTrace ,
281
348
) : boolean {
282
349
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.
285
355
firstFrame ++ ;
286
356
}
287
357
if (stack.length > firstFrame ) {
@@ -4213,7 +4283,8 @@ function serializeIONode(
4213
4283
let stack = null ;
4214
4284
let name = '';
4215
4285
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 ) ;
4217
4288
stack = filterStackTrace ( request , fullStack ) ;
4218
4289
name = findCalledFunctionNameFromStackTrace ( request , fullStack ) ;
4219
4290
// The name can include the object that this was called on but sometimes that's
0 commit comments