@@ -89,6 +89,7 @@ import {
8989 requestStorage ,
9090 createHints ,
9191 initAsyncDebugInfo ,
92+ markAsyncSequenceRootTask ,
9293 getCurrentAsyncSequence ,
9394 parseStackTrace ,
9495 supportsComponentStorage ,
@@ -149,7 +150,13 @@ import binaryToComparableString from 'shared/binaryToComparableString';
149150
150151import { SuspenseException , getSuspendedThenable } from './ReactFlightThenable' ;
151152
152- import { IO_NODE , PROMISE_NODE , AWAIT_NODE } from './ReactFlightAsyncSequence' ;
153+ import {
154+ IO_NODE ,
155+ PROMISE_NODE ,
156+ AWAIT_NODE ,
157+ UNRESOLVED_AWAIT_NODE ,
158+ UNRESOLVED_PROMISE_NODE ,
159+ } from './ReactFlightAsyncSequence' ;
153160
154161// DEV-only set containing internal objects that should not be limited and turned into getters.
155162const doNotLimit : WeakSet < Reference > = __DEV__ ? new WeakSet() : (null: any);
@@ -1879,6 +1886,9 @@ function visitAsyncNode(
18791886 case IO_NODE : {
18801887 return node ;
18811888 }
1889+ case UNRESOLVED_PROMISE_NODE : {
1890+ return null ;
1891+ }
18821892 case PROMISE_NODE : {
18831893 if ( node . end < cutOff ) {
18841894 // This was already resolved when we started this sequence. It must have been
@@ -1888,6 +1898,7 @@ function visitAsyncNode(
18881898 return null ;
18891899 }
18901900 const awaited = node . awaited ;
1901+ let match = null ;
18911902 if ( awaited !== null ) {
18921903 const ioNode = visitAsyncNode ( request , task , awaited , cutOff , visited ) ;
18931904 if ( ioNode !== null ) {
@@ -1907,72 +1918,104 @@ function visitAsyncNode(
19071918 // If we haven't defined an end time, use the resolve of the outer Promise.
19081919 ioNode . end = node . end ;
19091920 }
1910- return ioNode ;
1921+ match = ioNode ;
1922+ } else {
1923+ match = node ;
19111924 }
1912- return node ;
19131925 }
19141926 }
1915- return null ;
1927+ // We need to forward after we visit awaited nodes because what ever I/O we requested that's
1928+ // the thing that generated this node and its virtual children.
1929+ const debugInfo = node . debugInfo ;
1930+ if ( debugInfo !== null ) {
1931+ forwardDebugInfo ( request , task . id , debugInfo ) ;
1932+ }
1933+ return match ;
19161934 }
1935+ case UNRESOLVED_AWAIT_NODE :
1936+ // We could be inside the .then() which is about to resolve this node.
1937+ // TODO: We could call emitAsyncSequence in a microtask to avoid this issue.
1938+ // Fallthrough to the resolved path.
19171939 case AWAIT_NODE : {
19181940 const awaited = node . awaited ;
1941+ let match = null ;
19191942 if ( awaited !== null ) {
19201943 const ioNode = visitAsyncNode ( request , task , awaited , cutOff , visited ) ;
19211944 if ( ioNode !== null ) {
1922- if ( node . end < 0 ) {
1945+ let endTime : number ;
1946+ if ( node . tag === UNRESOLVED_AWAIT_NODE ) {
19231947 // If we haven't defined an end time, use the resolve of the inner Promise.
19241948 // This can happen because the ping gets invoked before the await gets resolved.
19251949 if ( ioNode . end < node . start ) {
19261950 // If we're awaiting a resolved Promise it could have finished before we started.
1927- node . end = node . start ;
1951+ endTime = node . start ;
19281952 } else {
1929- node . end = ioNode . end ;
1953+ endTime = ioNode . end ;
19301954 }
1955+ } else {
1956+ endTime = node . end ;
19311957 }
1932- if ( node . end < cutOff ) {
1958+ if ( endTime < cutOff ) {
19331959 // This was already resolved when we started this sequence. It must have been
19341960 // part of a different component.
19351961 // TODO: Think of some other way to exclude irrelevant data since if we awaited
19361962 // a cached promise, we should still log this component as being dependent on that data.
1937- return null ;
1938- }
1939-
1940- const stack = filterStackTrace (
1941- request ,
1942- parseStackTrace ( node . stack , 1 ) ,
1943- ) ;
1944- if ( stack . length === 0 ) {
1945- // If this await was fully filtered out, then it was inside third party code
1946- // such as in an external library. We return the I/O node and try another await.
1947- return ioNode ;
1948- }
1949- // Outline the IO node.
1950- serializeIONode ( request , ioNode ) ;
1951- // We log the environment at the time when the last promise pigned ping which may
1952- // be later than what the environment was when we actually started awaiting.
1953- const env = ( 0 , request . environmentName ) ( ) ;
1954- if ( node . start <= cutOff ) {
1955- // If this was an await that started before this sequence but finished after,
1956- // then we clamp it to the start of this sequence. We don't need to emit a time
1957- // TODO: Typically we'll already have a previous time stamp with the cutOff time
1958- // so we shouldn't need to emit another one. But not always.
1959- emitTimingChunk ( request , task . id , cutOff ) ;
19601963 } else {
1961- emitTimingChunk ( request , task . id , node . start ) ;
1964+ const stack = filterStackTrace (
1965+ request ,
1966+ parseStackTrace ( node . stack , 1 ) ,
1967+ ) ;
1968+ if ( stack . length === 0 ) {
1969+ // If this await was fully filtered out, then it was inside third party code
1970+ // such as in an external library. We return the I/O node and try another await.
1971+ match = ioNode ;
1972+ } else {
1973+ // Outline the IO node.
1974+ if ( ioNode . end < 0 ) {
1975+ ioNode . end = endTime ;
1976+ }
1977+ serializeIONode ( request , ioNode ) ;
1978+ // We log the environment at the time when the last promise pigned ping which may
1979+ // be later than what the environment was when we actually started awaiting.
1980+ const env = ( 0 , request . environmentName ) ( ) ;
1981+ if ( node . start <= cutOff ) {
1982+ // If this was an await that started before this sequence but finished after,
1983+ // then we clamp it to the start of this sequence. We don't need to emit a time
1984+ // TODO: Typically we'll already have a previous time stamp with the cutOff time
1985+ // so we shouldn't need to emit another one. But not always.
1986+ emitTimingChunk ( request , task . id , cutOff ) ;
1987+ } else {
1988+ emitTimingChunk ( request , task . id , node . start ) ;
1989+ }
1990+ // Then emit a reference to us awaiting it in the current task.
1991+ request . pendingChunks ++ ;
1992+ emitDebugChunk ( request , task . id , {
1993+ awaited : ( ( ioNode : any ) : ReactIOInfo ) , // This is deduped by this reference.
1994+ env : env ,
1995+ owner : node . owner ,
1996+ stack : stack ,
1997+ } ) ;
1998+ emitTimingChunk ( request , task . id , node . end ) ;
1999+ }
19622000 }
1963- // Then emit a reference to us awaiting it in the current task.
1964- request . pendingChunks ++ ;
1965- emitDebugChunk ( request , task . id , {
1966- awaited : ( ( ioNode : any ) : ReactIOInfo ) , // This is deduped by this reference.
1967- env : env ,
1968- owner : node . owner ,
1969- stack : stack ,
1970- } ) ;
1971- emitTimingChunk ( request , task . id , node . end ) ;
19722001 }
19732002 }
1974- // If we had awaited anything we would have written it now.
1975- return null ;
2003+ // We need to forward after we visit awaited nodes because what ever I/O we requested that's
2004+ // the thing that generated this node and its virtual children.
2005+ let debugInfo : null | ReactDebugInfo ;
2006+ if ( node . tag === UNRESOLVED_AWAIT_NODE ) {
2007+ const promise = node . debugInfo . deref ( ) ;
2008+ debugInfo =
2009+ promise === undefined || promise . _debugInfo === undefined
2010+ ? null
2011+ : promise . _debugInfo ;
2012+ } else {
2013+ debugInfo = node . debugInfo ;
2014+ }
2015+ if ( debugInfo !== null ) {
2016+ forwardDebugInfo ( request , task . id , debugInfo ) ;
2017+ }
2018+ return match ;
19762019 }
19772020 default : {
19782021 // eslint-disable-next-line react-internal/prod-error-codes
@@ -4513,6 +4556,8 @@ function tryStreamTask(request: Request, task: Task): void {
45134556}
45144557
45154558function performWork ( request : Request ) : void {
4559+ markAsyncSequenceRootTask ( ) ;
4560+
45164561 const prevDispatcher = ReactSharedInternals . H ;
45174562 ReactSharedInternals . H = HooksDispatcher ;
45184563 const prevRequest = currentRequest ;
0 commit comments