diff --git a/fixtures/flight/src/App.js b/fixtures/flight/src/App.js index 2f29de7abaffe..5657b040ffa2b 100644 --- a/fixtures/flight/src/App.js +++ b/fixtures/flight/src/App.js @@ -33,12 +33,22 @@ function Foo({children}) { return
{children}
; } +async function delayedError(text, ms) { + return new Promise((_, reject) => + setTimeout(() => reject(new Error(text)), ms) + ); +} + async function delay(text, ms) { return new Promise(resolve => setTimeout(() => resolve(text), ms)); } async function delayTwice() { - await delay('', 20); + try { + await delayedError('Delayed exception', 20); + } catch (x) { + // Ignored + } await delay('', 10); } @@ -113,6 +123,7 @@ async function ServerComponent({noCache}) { export default async function App({prerender, noCache}) { const res = await fetch('http://localhost:3001/todos'); const todos = await res.json(); + console.log(res); const dedupedChild = ; const message = getServerState(); diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js index 42f697c94a869..01e40cdc551ec 100644 --- a/packages/react-client/src/ReactFlightClient.js +++ b/packages/react-client/src/ReactFlightClient.js @@ -79,7 +79,9 @@ import { logDedupedComponentRender, logComponentErrored, logIOInfo, + logIOInfoErrored, logComponentAwait, + logComponentAwaitErrored, } from './ReactFlightPerformanceTrack'; import { @@ -96,6 +98,8 @@ import {getOwnerStackByComponentInfoInDev} from 'shared/ReactComponentInfoStack' import {injectInternals} from './ReactFlightClientDevToolsHook'; +import {OMITTED_PROP_ERROR} from './ReactFlightPropertyAccess'; + import ReactVersion from 'shared/ReactVersion'; import isArray from 'shared/isArray'; @@ -1684,11 +1688,7 @@ function parseModelString( Object.defineProperty(parentObject, key, { get: function () { // TODO: We should ideally throw here to indicate a difference. - return ( - 'This object has been omitted by React in the console log ' + - 'to avoid sending too much data from the server. Try logging smaller ' + - 'or more specific objects.' - ); + return OMITTED_PROP_ERROR; }, enumerable: true, configurable: false, @@ -2909,7 +2909,29 @@ function initializeIOInfo(response: Response, ioInfo: ReactIOInfo): void { // $FlowFixMe[cannot-write] ioInfo.end += response._timeOrigin; - logIOInfo(ioInfo, response._rootEnvironmentName); + const env = response._rootEnvironmentName; + const promise = ioInfo.value; + if (promise) { + const thenable: Thenable = (promise: any); + switch (thenable.status) { + case INITIALIZED: + logIOInfo(ioInfo, env, thenable.value); + break; + case ERRORED: + logIOInfoErrored(ioInfo, env, thenable.reason); + break; + default: + // If we haven't resolved the Promise yet, wait to log until have so we can include + // its data in the log. + promise.then( + logIOInfo.bind(null, ioInfo, env), + logIOInfoErrored.bind(null, ioInfo, env), + ); + break; + } + } else { + logIOInfo(ioInfo, env, undefined); + } } function resolveIOInfo( @@ -3193,13 +3215,55 @@ function flushComponentPerformance( } // $FlowFixMe: Refined. const asyncInfo: ReactAsyncInfo = candidateInfo; - logComponentAwait( - asyncInfo, - trackIdx, - time, - endTime, - response._rootEnvironmentName, - ); + const env = response._rootEnvironmentName; + const promise = asyncInfo.awaited.value; + if (promise) { + const thenable: Thenable = (promise: any); + switch (thenable.status) { + case INITIALIZED: + logComponentAwait( + asyncInfo, + trackIdx, + time, + endTime, + env, + thenable.value, + ); + break; + case ERRORED: + logComponentAwaitErrored( + asyncInfo, + trackIdx, + time, + endTime, + env, + thenable.reason, + ); + break; + default: + // We assume that we should have received the data by now since this is logged at the + // end of the response stream. This is more sensitive to ordering so we don't wait + // to log it. + logComponentAwait( + asyncInfo, + trackIdx, + time, + endTime, + env, + undefined, + ); + break; + } + } else { + logComponentAwait( + asyncInfo, + trackIdx, + time, + endTime, + env, + undefined, + ); + } } } } diff --git a/packages/react-client/src/ReactFlightPerformanceTrack.js b/packages/react-client/src/ReactFlightPerformanceTrack.js index faa5cf9650d9c..76b23a15fdcd7 100644 --- a/packages/react-client/src/ReactFlightPerformanceTrack.js +++ b/packages/react-client/src/ReactFlightPerformanceTrack.js @@ -17,14 +17,143 @@ import type { import {enableProfilerTimer} from 'shared/ReactFeatureFlags'; +import {OMITTED_PROP_ERROR} from './ReactFlightPropertyAccess'; + +import hasOwnProperty from 'shared/hasOwnProperty'; +import isArray from 'shared/isArray'; + const supportsUserTiming = enableProfilerTimer && typeof console !== 'undefined' && - typeof console.timeStamp === 'function'; + typeof console.timeStamp === 'function' && + typeof performance !== 'undefined' && + // $FlowFixMe[method-unbinding] + typeof performance.measure === 'function'; const IO_TRACK = 'Server Requests ⚛'; const COMPONENTS_TRACK = 'Server Components ⚛'; +const EMPTY_ARRAY = 0; +const COMPLEX_ARRAY = 1; +const PRIMITIVE_ARRAY = 2; // Primitive values only +const ENTRIES_ARRAY = 3; // Tuple arrays of string and value (like Headers, Map, etc) +function getArrayKind(array: Object): 0 | 1 | 2 | 3 { + let kind = EMPTY_ARRAY; + for (let i = 0; i < array.length; i++) { + const value = array[i]; + if (typeof value === 'object' && value !== null) { + if ( + isArray(value) && + value.length === 2 && + typeof value[0] === 'string' + ) { + // Key value tuple + if (kind !== EMPTY_ARRAY && kind !== ENTRIES_ARRAY) { + return COMPLEX_ARRAY; + } + kind = ENTRIES_ARRAY; + } else { + return COMPLEX_ARRAY; + } + } else if (typeof value === 'function') { + return COMPLEX_ARRAY; + } else if (typeof value === 'string' && value.length > 50) { + return COMPLEX_ARRAY; + } else if (kind !== EMPTY_ARRAY && kind !== PRIMITIVE_ARRAY) { + return COMPLEX_ARRAY; + } else { + kind = PRIMITIVE_ARRAY; + } + } + return kind; +} + +function addObjectToProperties( + object: Object, + properties: Array<[string, string]>, + indent: number, +): void { + for (const key in object) { + if (hasOwnProperty.call(object, key) && key[0] !== '_') { + const value = object[key]; + addValueToProperties(key, value, properties, indent); + } + } +} + +function addValueToProperties( + propertyName: string, + value: mixed, + properties: Array<[string, string]>, + indent: number, +): void { + let desc; + switch (typeof value) { + case 'object': + if (value === null) { + desc = 'null'; + break; + } else { + // $FlowFixMe[method-unbinding] + const objectToString = Object.prototype.toString.call(value); + let objectName = objectToString.slice(8, objectToString.length - 1); + if (objectName === 'Array') { + const array: Array = (value: any); + const kind = getArrayKind(array); + if (kind === PRIMITIVE_ARRAY || kind === EMPTY_ARRAY) { + desc = JSON.stringify(array); + break; + } else if (kind === ENTRIES_ARRAY) { + properties.push(['\xa0\xa0'.repeat(indent) + propertyName, '']); + for (let i = 0; i < array.length; i++) { + const entry = array[i]; + addValueToProperties(entry[0], entry[1], properties, indent + 1); + } + return; + } + } + if (objectName === 'Object') { + const proto: any = Object.getPrototypeOf(value); + if (proto && typeof proto.constructor === 'function') { + objectName = proto.constructor.name; + } + } + properties.push([ + '\xa0\xa0'.repeat(indent) + propertyName, + objectName === 'Object' ? '' : objectName, + ]); + if (indent < 3) { + addObjectToProperties(value, properties, indent + 1); + } + return; + } + case 'function': + if (value.name === '') { + desc = '() => {}'; + } else { + desc = value.name + '() {}'; + } + break; + case 'string': + if (value === OMITTED_PROP_ERROR) { + desc = '...'; + } else { + desc = JSON.stringify(value); + } + break; + case 'undefined': + desc = 'undefined'; + break; + case 'boolean': + desc = value ? 'true' : 'false'; + break; + default: + // eslint-disable-next-line react-internal/safe-string-coercion + desc = String(value); + } + properties.push(['\xa0\xa0'.repeat(indent) + propertyName, desc]); +} + export function markAllTracksInOrder() { if (supportsUserTiming) { // Ensure we create the Server Component track groups earlier than the Client Scheduler @@ -133,12 +262,7 @@ export function logComponentErrored( const isPrimaryEnv = env === rootEnv; const entryName = isPrimaryEnv || env === undefined ? name : name + ' [' + env + ']'; - if ( - __DEV__ && - typeof performance !== 'undefined' && - // $FlowFixMe[method-unbinding] - typeof performance.measure === 'function' - ) { + if (__DEV__) { const message = typeof error === 'object' && error !== null && @@ -228,12 +352,68 @@ function getIOColor( } } +export function logComponentAwaitErrored( + asyncInfo: ReactAsyncInfo, + trackIdx: number, + startTime: number, + endTime: number, + rootEnv: string, + error: mixed, +): void { + if (supportsUserTiming && endTime > 0) { + const env = asyncInfo.env; + const name = asyncInfo.awaited.name; + const isPrimaryEnv = env === rootEnv; + const entryName = + 'await ' + + (isPrimaryEnv || env === undefined ? name : name + ' [' + env + ']'); + const debugTask = asyncInfo.debugTask; + if (__DEV__ && debugTask) { + const message = + typeof error === 'object' && + error !== null && + typeof error.message === 'string' + ? // eslint-disable-next-line react-internal/safe-string-coercion + String(error.message) + : // eslint-disable-next-line react-internal/safe-string-coercion + String(error); + const properties = [['Rejected', message]]; + debugTask.run( + // $FlowFixMe[method-unbinding] + performance.measure.bind(performance, entryName, { + start: startTime < 0 ? 0 : startTime, + end: endTime, + detail: { + devtools: { + color: 'error', + track: trackNames[trackIdx], + trackGroup: COMPONENTS_TRACK, + properties, + tooltipText: entryName + ' Rejected', + }, + }, + }), + ); + } else { + console.timeStamp( + entryName, + startTime < 0 ? 0 : startTime, + endTime, + trackNames[trackIdx], + COMPONENTS_TRACK, + 'error', + ); + } + } +} + export function logComponentAwait( asyncInfo: ReactAsyncInfo, trackIdx: number, startTime: number, endTime: number, rootEnv: string, + value: mixed, ): void { if (supportsUserTiming && endTime > 0) { const env = asyncInfo.env; @@ -245,17 +425,26 @@ export function logComponentAwait( (isPrimaryEnv || env === undefined ? name : name + ' [' + env + ']'); const debugTask = asyncInfo.debugTask; if (__DEV__ && debugTask) { + const properties: Array<[string, string]> = []; + if (typeof value === 'object' && value !== null) { + addObjectToProperties(value, properties, 0); + } else if (value !== undefined) { + addValueToProperties('Resolved', value, properties, 0); + } debugTask.run( // $FlowFixMe[method-unbinding] - console.timeStamp.bind( - console, - entryName, - startTime < 0 ? 0 : startTime, - endTime, - trackNames[trackIdx], - COMPONENTS_TRACK, - color, - ), + performance.measure.bind(performance, entryName, { + start: startTime < 0 ? 0 : startTime, + end: endTime, + detail: { + devtools: { + color: color, + track: trackNames[trackIdx], + trackGroup: COMPONENTS_TRACK, + properties, + }, + }, + }), ); } else { console.timeStamp( @@ -270,7 +459,63 @@ export function logComponentAwait( } } -export function logIOInfo(ioInfo: ReactIOInfo, rootEnv: string): void { +export function logIOInfoErrored( + ioInfo: ReactIOInfo, + rootEnv: string, + error: mixed, +): void { + const startTime = ioInfo.start; + const endTime = ioInfo.end; + if (supportsUserTiming && endTime >= 0) { + const name = ioInfo.name; + const env = ioInfo.env; + const isPrimaryEnv = env === rootEnv; + const entryName = + isPrimaryEnv || env === undefined ? name : name + ' [' + env + ']'; + const debugTask = ioInfo.debugTask; + if (__DEV__ && debugTask) { + const message = + typeof error === 'object' && + error !== null && + typeof error.message === 'string' + ? // eslint-disable-next-line react-internal/safe-string-coercion + String(error.message) + : // eslint-disable-next-line react-internal/safe-string-coercion + String(error); + const properties = [['Rejected', message]]; + debugTask.run( + // $FlowFixMe[method-unbinding] + performance.measure.bind(performance, entryName, { + start: startTime < 0 ? 0 : startTime, + end: endTime, + detail: { + devtools: { + color: 'error', + track: IO_TRACK, + properties, + tooltipText: entryName + ' Rejected', + }, + }, + }), + ); + } else { + console.timeStamp( + entryName, + startTime < 0 ? 0 : startTime, + endTime, + IO_TRACK, + undefined, + 'error', + ); + } + } +} + +export function logIOInfo( + ioInfo: ReactIOInfo, + rootEnv: string, + value: mixed, +): void { const startTime = ioInfo.start; const endTime = ioInfo.end; if (supportsUserTiming && endTime >= 0) { @@ -282,17 +527,25 @@ export function logIOInfo(ioInfo: ReactIOInfo, rootEnv: string): void { const debugTask = ioInfo.debugTask; const color = getIOColor(name); if (__DEV__ && debugTask) { + const properties: Array<[string, string]> = []; + if (typeof value === 'object' && value !== null) { + addObjectToProperties(value, properties, 0); + } else if (value !== undefined) { + addValueToProperties('Resolved', value, properties, 0); + } debugTask.run( // $FlowFixMe[method-unbinding] - console.timeStamp.bind( - console, - entryName, - startTime < 0 ? 0 : startTime, - endTime, - IO_TRACK, - undefined, - color, - ), + performance.measure.bind(performance, entryName, { + start: startTime < 0 ? 0 : startTime, + end: endTime, + detail: { + devtools: { + color: color, + track: IO_TRACK, + properties, + }, + }, + }), ); } else { console.timeStamp( diff --git a/packages/react-client/src/ReactFlightPropertyAccess.js b/packages/react-client/src/ReactFlightPropertyAccess.js new file mode 100644 index 0000000000000..16f8e8cbfcade --- /dev/null +++ b/packages/react-client/src/ReactFlightPropertyAccess.js @@ -0,0 +1,13 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +export const OMITTED_PROP_ERROR = + 'This object has been omitted by React in the console log ' + + 'to avoid sending too much data from the server. Try logging smaller ' + + 'or more specific objects.'; diff --git a/packages/react-server/src/ReactFlightAsyncSequence.js b/packages/react-server/src/ReactFlightAsyncSequence.js index 6c44e52a07fa6..488e8e3ad2662 100644 --- a/packages/react-server/src/ReactFlightAsyncSequence.js +++ b/packages/react-server/src/ReactFlightAsyncSequence.js @@ -27,9 +27,9 @@ export type IONode = { tag: 0, owner: null | ReactComponentInfo, stack: ReactStackTrace, // callsite that spawned the I/O - debugInfo: null, // not used on I/O start: number, // start time when the first part of the I/O sequence started end: number, // we typically don't use this. only when there's no promise intermediate. + promise: null, // not used on I/O awaited: null, // I/O is only blocked on external. previous: null | AwaitNode | UnresolvedAwaitNode, // the preceeding await that spawned this new work }; @@ -37,10 +37,10 @@ export type IONode = { export type PromiseNode = { tag: 1, owner: null | ReactComponentInfo, - debugInfo: null | ReactDebugInfo, // forwarded debugInfo from the Promise stack: ReactStackTrace, // callsite that created the Promise start: number, // start time when the Promise was created end: number, // end time when the Promise was resolved. + promise: WeakRef, // a reference to this Promise if still referenced awaited: null | AsyncSequence, // the thing that ended up resolving this promise previous: null | AsyncSequence, // represents what the last return of an async function depended on before returning }; @@ -48,10 +48,10 @@ export type PromiseNode = { export type AwaitNode = { tag: 2, owner: null | ReactComponentInfo, - debugInfo: null | ReactDebugInfo, // forwarded debugInfo from the Promise stack: ReactStackTrace, // callsite that awaited (using await, .then(), Promise.all(), ...) start: number, // when we started blocking. This might be later than the I/O started. end: number, // when we unblocked. This might be later than the I/O resolved if there's CPU time. + promise: WeakRef, // a reference to this Promise if still referenced awaited: null | AsyncSequence, // the promise we were waiting on previous: null | AsyncSequence, // the sequence that was blocking us from awaiting in the first place }; @@ -59,10 +59,10 @@ export type AwaitNode = { export type UnresolvedPromiseNode = { tag: 3, owner: null | ReactComponentInfo, - debugInfo: WeakRef, // holds onto the Promise until we can extract debugInfo when it resolves stack: ReactStackTrace, // callsite that created the Promise start: number, // start time when the Promise was created end: -1.1, // set when we resolve. + promise: WeakRef, // a reference to this Promise if still referenced awaited: null | AsyncSequence, // the thing that ended up resolving this promise previous: null, // where we created the promise is not interesting since creating it doesn't mean waiting. }; @@ -70,10 +70,10 @@ export type UnresolvedPromiseNode = { export type UnresolvedAwaitNode = { tag: 4, owner: null | ReactComponentInfo, - debugInfo: WeakRef, // holds onto the Promise until we can extract debugInfo when it resolves stack: ReactStackTrace, // callsite that awaited (using await, .then(), Promise.all(), ...) start: number, // when we started blocking. This might be later than the I/O started. end: -1.1, // set when we resolve. + promise: WeakRef, // a reference to this Promise if still referenced awaited: null | AsyncSequence, // the promise we were waiting on previous: null | AsyncSequence, // the sequence that was blocking us from awaiting in the first place }; diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index eb64ce9d3de6e..f07e3c6304582 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -2053,10 +2053,13 @@ function visitAsyncNode( } // We need to forward after we visit awaited nodes because what ever I/O we requested that's // the thing that generated this node and its virtual children. - const debugInfo = node.debugInfo; - if (debugInfo !== null && !visited.has(debugInfo)) { - visited.add(debugInfo); - forwardDebugInfo(request, task, debugInfo); + const promise = node.promise.deref(); + if (promise !== undefined) { + const debugInfo = promise._debugInfo; + if (debugInfo != null && !visited.has(debugInfo)) { + visited.add(debugInfo); + forwardDebugInfo(request, task, debugInfo); + } } return match; } @@ -2084,14 +2087,32 @@ function visitAsyncNode( // just part of a previous component's rendering. match = ioNode; } else { - const stack = filterStackTrace(request, node.stack); - if (stack.length === 0) { + let isAwaitInUserspace = false; + const fullStack = node.stack; + if (fullStack.length > 0) { + // Check if the very first stack frame that awaited this Promise was in user space. + // TODO: This doesn't take into account wrapper functions such as our fake .then() + // in FlightClient which will always be considered third party awaits if you call + // .then directly. + const filterStackFrame = request.filterStackFrame; + const callsite = fullStack[0]; + const functionName = callsite[0]; + const url = devirtualizeURL(callsite[1]); + isAwaitInUserspace = filterStackFrame(url, functionName); + } + if (!isAwaitInUserspace) { // If this await was fully filtered out, then it was inside third party code // such as in an external library. We return the I/O node and try another await. match = ioNode; } else { + // We found a user space await. + // Outline the IO node. - serializeIONode(request, ioNode); + // The ioNode is where the I/O was initiated, but after that it could have been + // processed through various awaits in the internals of the third party code. + // Therefore we don't use the inner most Promise as the conceptual value but the + // Promise that was ultimately awaited by the user space await. + serializeIONode(request, ioNode, awaited.promise); // We log the environment at the time when the last promise pigned ping which may // be later than what the environment was when we actually started awaiting. @@ -2103,7 +2124,7 @@ function visitAsyncNode( awaited: ((ioNode: any): ReactIOInfo), // This is deduped by this reference. env: env, owner: node.owner, - stack: stack, + stack: filterStackTrace(request, node.stack), }); markOperationEndTime(request, task, endTime); } @@ -2112,10 +2133,13 @@ function visitAsyncNode( } // We need to forward after we visit awaited nodes because what ever I/O we requested that's // the thing that generated this node and its virtual children. - const debugInfo = node.debugInfo; - if (debugInfo !== null && !visited.has(debugInfo)) { - visited.add(debugInfo); - forwardDebugInfo(request, task, debugInfo); + const promise = node.promise.deref(); + if (promise !== undefined) { + const debugInfo = promise._debugInfo; + if (debugInfo != null && !visited.has(debugInfo)) { + visited.add(debugInfo); + forwardDebugInfo(request, task, debugInfo); + } } return match; } @@ -2141,7 +2165,7 @@ function emitAsyncSequence( const awaitedNode = visitAsyncNode(request, task, node, visited, task.time); if (awaitedNode !== null) { // Nothing in user space (unfiltered stack) awaited this. - serializeIONode(request, awaitedNode); + serializeIONode(request, awaitedNode, awaitedNode.promise); request.pendingChunks++; // We log the environment at the time when we ping which may be later than what the // environment was when we actually started awaiting. @@ -3726,6 +3750,7 @@ function emitIOInfoChunk( name: string, start: number, end: number, + value: ?Promise, env: ?string, owner: ?ReactComponentInfo, stack: ?ReactStackTrace, @@ -3750,6 +3775,10 @@ function emitIOInfoChunk( start: relativeStartTimestamp, end: relativeEndTimestamp, }; + if (value !== undefined) { + // $FlowFixMe[cannot-write] + debugIOInfo.value = value; + } if (env != null) { // $FlowFixMe[cannot-write] debugIOInfo.env = env; @@ -3797,6 +3826,7 @@ function outlineIOInfo(request: Request, ioInfo: ReactIOInfo): void { ioInfo.name, ioInfo.start, ioInfo.end, + ioInfo.value, ioInfo.env, owner, debugStack, @@ -3807,6 +3837,7 @@ function outlineIOInfo(request: Request, ioInfo: ReactIOInfo): void { function serializeIONode( request: Request, ioNode: IONode | PromiseNode, + promiseRef: null | WeakRef>, ): string { const existingRef = request.writtenDebugObjects.get(ioNode); if (existingRef !== undefined) { @@ -3834,6 +3865,11 @@ function serializeIONode( outlineComponentInfo(request, owner); } + let value: void | Promise = undefined; + if (promiseRef !== null) { + value = promiseRef.deref(); + } + // We log the environment at the time when we serialize the I/O node. // The environment name may have changed from when the I/O was actually started. const env = (0, request.environmentName)(); @@ -3846,6 +3882,7 @@ function serializeIONode( name, ioNode.start, ioNode.end, + value, env, owner, stack, diff --git a/packages/react-server/src/ReactFlightServerConfigDebugNode.js b/packages/react-server/src/ReactFlightServerConfigDebugNode.js index e0982621f4786..d42e9b2bb0a97 100644 --- a/packages/react-server/src/ReactFlightServerConfigDebugNode.js +++ b/packages/react-server/src/ReactFlightServerConfigDebugNode.js @@ -34,6 +34,23 @@ const getAsyncId = AsyncResource.prototype.asyncId; const pendingOperations: Map = __DEV__ && enableAsyncDebugInfo ? new Map() : (null: any); +// This is a weird one. This map, keeps a dependent Promise alive if the child Promise is still alive. +// A PromiseNode/AwaitNode cannot hold a strong reference to its own Promise because then it'll never get +// GC:ed. We only need it if a dependent AwaitNode points to it. We could put a reference in the Node +// but that would require a GC pass between every Node that gets destroyed. I.e. the root gets destroy() +// called on it and then that release it from the pendingOperations map which allows the next one to GC +// and so on. By putting this relationship in a WeakMap this could be done as a single pass in the VM. +// We don't actually ever have to read from this map since we have WeakRef reference to these Promises +// if they're still alive. It's also optional information so we could just expose only if GC didn't run. +const awaitedPromise: WeakMap, Promise> = __DEV__ && +enableAsyncDebugInfo + ? new WeakMap() + : (null: any); +const previousPromise: WeakMap, Promise> = __DEV__ && +enableAsyncDebugInfo + ? new WeakMap() + : (null: any); + // Keep the last resolved await as a workaround for async functions missing data. let lastRanAwait: null | AwaitNode = null; @@ -45,12 +62,6 @@ function resolvePromiseOrAwaitNode( resolvedNode.tag = ((unresolvedNode.tag === UNRESOLVED_PROMISE_NODE ? PROMISE_NODE : AWAIT_NODE): any); - // The Promise can be garbage collected after this so we should extract debugInfo first. - const promise = unresolvedNode.debugInfo.deref(); - resolvedNode.debugInfo = - promise === undefined || promise._debugInfo === undefined - ? null - : promise._debugInfo; resolvedNode.end = endTime; return resolvedNode; } @@ -72,6 +83,14 @@ export function initAsyncDebugInfo(): void { const trigger = pendingOperations.get(triggerAsyncId); let node: AsyncSequence; if (type === 'PROMISE') { + if (trigger !== undefined && trigger.promise !== null) { + const triggerPromise = trigger.promise.deref(); + if (triggerPromise !== undefined) { + // Keep the awaited Promise alive as long as the child is alive so we can + // trace its value at the end. + awaitedPromise.set(resource, triggerPromise); + } + } const currentAsyncId = executionAsyncId(); if (currentAsyncId !== triggerAsyncId) { // When you call .then() on a native Promise, or await/Promise.all() a thenable, @@ -81,15 +100,23 @@ export function initAsyncDebugInfo(): void { return; } const current = pendingOperations.get(currentAsyncId); + if (current !== undefined && current.promise !== null) { + const currentPromise = current.promise.deref(); + if (currentPromise !== undefined) { + // Keep the previous Promise alive as long as the child is alive so we can + // trace its value at the end. + previousPromise.set(resource, currentPromise); + } + } // If the thing we're waiting on is another Await we still track that sequence // so that we can later pick the best stack trace in user space. node = ({ tag: UNRESOLVED_AWAIT_NODE, owner: resolveOwner(), - debugInfo: new WeakRef((resource: Promise)), - stack: parseStackTrace(new Error(), 1), + stack: parseStackTrace(new Error(), 5), start: performance.now(), end: -1.1, // set when resolved. + promise: new WeakRef((resource: Promise)), awaited: trigger, // The thing we're awaiting on. Might get overrriden when we resolve. previous: current === undefined ? null : current, // The path that led us here. }: UnresolvedAwaitNode); @@ -97,10 +124,10 @@ export function initAsyncDebugInfo(): void { node = ({ tag: UNRESOLVED_PROMISE_NODE, owner: resolveOwner(), - debugInfo: new WeakRef((resource: Promise)), - stack: parseStackTrace(new Error(), 1), + stack: parseStackTrace(new Error(), 5), start: performance.now(), end: -1.1, // Set when we resolve. + promise: new WeakRef((resource: Promise)), awaited: trigger === undefined ? null // It might get overridden when we resolve. @@ -118,10 +145,10 @@ export function initAsyncDebugInfo(): void { node = ({ tag: IO_NODE, owner: resolveOwner(), - debugInfo: null, - stack: parseStackTrace(new Error(), 1), // This is only used if no native promises are used. + stack: parseStackTrace(new Error(), 3), // This is only used if no native promises are used. start: performance.now(), end: -1.1, // Only set when pinged. + promise: null, awaited: null, previous: null, }: IONode); @@ -133,10 +160,10 @@ export function initAsyncDebugInfo(): void { node = ({ tag: IO_NODE, owner: resolveOwner(), - debugInfo: null, - stack: parseStackTrace(new Error(), 1), + stack: parseStackTrace(new Error(), 3), start: performance.now(), end: -1.1, // Only set when pinged. + promise: null, awaited: null, previous: trigger, }: IONode); diff --git a/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js b/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js index 42fa56a836a2d..7b5a284bd916d 100644 --- a/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js +++ b/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js @@ -50,6 +50,23 @@ function normalizeIOInfo(ioInfo) { if (typeof ioInfo.end === 'number') { copy.end = 0; } + const promise = ioInfo.value; + if (promise) { + promise.then(); // init + if (promise.status === 'fulfilled') { + copy.value = { + value: promise.value, + }; + } else if (promise.status === 'rejected') { + copy.value = { + reason: promise.reason, + }; + } else { + copy.value = { + status: promise.status, + }; + } + } return copy; } @@ -129,6 +146,16 @@ describe('ReactFlightAsyncDebugInfo', () => { Stream = require('stream'); }); + function finishLoadingStream(readable) { + return new Promise(resolve => { + if (readable.readableEnded) { + resolve(); + } else { + readable.on('end', () => resolve()); + } + }); + } + function delay(timeout) { return new Promise(resolve => { setTimeout(resolve, timeout); @@ -183,6 +210,8 @@ describe('ReactFlightAsyncDebugInfo', () => { stream.pipe(readable); expect(await result).toBe('HI, SEB'); + + await finishLoadingStream(readable); if ( __DEV__ && gate( @@ -204,9 +233,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 175, + 202, 109, - 155, + 182, 50, ], ], @@ -228,9 +257,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 175, + 202, 109, - 155, + 182, 50, ], ], @@ -239,29 +268,32 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 133, + 160, 12, - 132, + 159, 3, ], [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 157, + 184, 13, - 156, + 183, 5, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 164, + 191, 26, - 163, + 190, 5, ], ], "start": 0, + "value": { + "value": undefined, + }, }, "env": "Server", "owner": { @@ -273,9 +305,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 175, + 202, 109, - 155, + 182, 50, ], ], @@ -284,17 +316,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 157, + 184, 13, - 156, + 183, 5, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 164, + 191, 26, - 163, + 190, 5, ], ], @@ -319,9 +351,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 175, + 202, 109, - 155, + 182, 50, ], ], @@ -330,29 +362,34 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 133, + 160, 12, - 132, + 159, 3, ], [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 158, + 185, 21, - 156, + 183, 5, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 164, + 191, 20, - 163, + 190, 5, ], ], "start": 0, + "value": { + "value": [ + , + ], + }, }, "env": "Server", "owner": { @@ -364,9 +401,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 175, + 202, 109, - 155, + 182, 50, ], ], @@ -375,17 +412,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 159, + 186, 21, - 156, + 183, 5, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 164, + 191, 20, - 163, + 190, 5, ], ], @@ -405,9 +442,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 166, + 193, 60, - 163, + 190, 5, ], ], @@ -429,9 +466,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 175, + 202, 109, - 155, + 182, 50, ], ], @@ -440,21 +477,24 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 133, + 160, 12, - 132, + 159, 3, ], [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 158, + 185, 21, - 156, + 183, 5, ], ], "start": 0, + "value": { + "status": "halted", + }, }, "env": "Server", "owner": { @@ -466,9 +506,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 166, + 193, 60, - 163, + 190, 5, ], ], @@ -477,9 +517,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "InnerComponent", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 172, + 199, 35, - 169, + 196, 5, ], ], @@ -530,6 +570,8 @@ describe('ReactFlightAsyncDebugInfo', () => { stream.pipe(readable); expect(await result).toBe('HI, SEB'); + + await finishLoadingStream(readable); if ( __DEV__ && gate( @@ -551,9 +593,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 517, + 557, 40, - 498, + 538, 49, ], ], @@ -575,9 +617,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 517, + 557, 40, - 498, + 538, 49, ], ], @@ -586,29 +628,32 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 133, + 160, 12, - 132, + 159, 3, ], [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 500, + 540, 13, - 499, + 539, 5, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 505, + 545, 36, - 504, + 544, 5, ], ], "start": 0, + "value": { + "value": undefined, + }, }, "env": "Server", "owner": { @@ -620,9 +665,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 517, + 557, 40, - 498, + 538, 49, ], ], @@ -631,17 +676,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 500, + 540, 13, - 499, + 539, 5, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 505, + 545, 36, - 504, + 544, 5, ], ], @@ -661,9 +706,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 507, + 547, 60, - 504, + 544, 5, ], ], @@ -682,9 +727,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 517, + 557, 40, - 498, + 538, 49, ], ], @@ -693,29 +738,32 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 133, + 160, 12, - 132, + 159, 3, ], [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 500, + 540, 13, - 499, + 539, 5, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 506, + 546, 22, - 504, + 544, 5, ], ], "start": 0, + "value": { + "value": undefined, + }, }, "env": "Server", "owner": { @@ -727,9 +775,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 507, + 547, 60, - 504, + 544, 5, ], ], @@ -738,9 +786,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "InnerComponent", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 513, + 553, 40, - 510, + 550, 5, ], ], @@ -780,6 +828,8 @@ describe('ReactFlightAsyncDebugInfo', () => { stream.pipe(readable); expect(await result).toBe('hi'); + + await finishLoadingStream(readable); if ( __DEV__ && gate( @@ -801,9 +851,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 772, + 820, 109, - 759, + 807, 67, ], ], @@ -822,9 +872,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 772, + 820, 109, - 759, + 807, 67, ], ], @@ -833,9 +883,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 762, + 810, 7, - 760, + 808, 5, ], ], @@ -874,6 +924,8 @@ describe('ReactFlightAsyncDebugInfo', () => { stream.pipe(readable); expect(await result).toBe('hi'); + + await finishLoadingStream(readable); if ( __DEV__ && gate( @@ -895,9 +947,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 866, + 916, 109, - 857, + 907, 94, ], ], @@ -945,6 +997,8 @@ describe('ReactFlightAsyncDebugInfo', () => { stream.pipe(readable); expect(await result).toBe('HI'); + + await finishLoadingStream(readable); if ( __DEV__ && gate( @@ -966,9 +1020,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 937, + 989, 109, - 913, + 965, 50, ], ], @@ -1027,6 +1081,8 @@ describe('ReactFlightAsyncDebugInfo', () => { stream.pipe(readable); expect(await result).toBe('HI'); + + await finishLoadingStream(readable); if ( __DEV__ && gate( @@ -1048,9 +1104,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1019, + 1073, 109, - 1002, + 1056, 63, ], ], @@ -1067,17 +1123,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "fetchThirdParty", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 140, + 167, 40, - 138, + 165, 3, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1015, + 1069, 24, - 1014, + 1068, 5, ], ], @@ -1099,17 +1155,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "fetchThirdParty", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 140, + 167, 40, - 138, + 165, 3, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1015, + 1069, 24, - 1014, + 1068, 5, ], ], @@ -1118,29 +1174,32 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 133, + 160, 12, - 132, + 159, 3, ], [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1004, + 1058, 13, - 1003, + 1057, 5, ], [ "ThirdPartyComponent", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1010, + 1064, 24, - 1009, + 1063, 5, ], ], "start": 0, + "value": { + "value": undefined, + }, }, "env": "third-party", "owner": { @@ -1152,17 +1211,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "fetchThirdParty", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 140, + 167, 40, - 138, + 165, 3, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1015, + 1069, 24, - 1014, + 1068, 5, ], ], @@ -1171,17 +1230,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1004, + 1058, 13, - 1003, + 1057, 5, ], [ "ThirdPartyComponent", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1010, + 1064, 24, - 1009, + 1063, 5, ], ], @@ -1206,17 +1265,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "fetchThirdParty", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 140, + 167, 40, - 138, + 165, 3, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1015, + 1069, 24, - 1014, + 1068, 5, ], ], @@ -1225,29 +1284,32 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 133, + 160, 12, - 132, + 159, 3, ], [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1005, + 1059, 13, - 1003, + 1057, 5, ], [ "ThirdPartyComponent", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1010, + 1064, 18, - 1009, + 1063, 5, ], ], "start": 0, + "value": { + "value": undefined, + }, }, "env": "third-party", "owner": { @@ -1259,17 +1321,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "fetchThirdParty", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 140, + 167, 40, - 138, + 165, 3, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1015, + 1069, 24, - 1014, + 1068, 5, ], ], @@ -1278,17 +1340,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1005, + 1059, 13, - 1003, + 1057, 5, ], [ "ThirdPartyComponent", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1010, + 1064, 18, - 1009, + 1063, 5, ], ], @@ -1340,6 +1402,8 @@ describe('ReactFlightAsyncDebugInfo', () => { stream.pipe(readable); expect(await result).toBe('HI, Seb'); + + await finishLoadingStream(readable); if ( __DEV__ && gate( @@ -1361,9 +1425,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1327, + 1389, 40, - 1310, + 1372, 62, ], ], @@ -1385,9 +1449,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1327, + 1389, 40, - 1310, + 1372, 62, ], ], @@ -1396,29 +1460,32 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 133, + 160, 12, - 132, + 159, 3, ], [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1312, + 1374, 13, - 1311, + 1373, 25, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1322, + 1384, 13, - 1321, + 1383, 5, ], ], "start": 0, + "value": { + "value": undefined, + }, }, "env": "Server", "owner": { @@ -1430,9 +1497,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1327, + 1389, 40, - 1310, + 1372, 62, ], ], @@ -1441,17 +1508,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1312, + 1374, 13, - 1311, + 1373, 25, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1322, + 1384, 13, - 1321, + 1383, 5, ], ], @@ -1471,9 +1538,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1323, + 1385, 60, - 1321, + 1383, 5, ], ], @@ -1495,9 +1562,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1327, + 1389, 40, - 1310, + 1372, 62, ], ], @@ -1506,29 +1573,32 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 133, + 160, 12, - 132, + 159, 3, ], [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1312, + 1374, 13, - 1311, + 1373, 25, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1322, + 1384, 13, - 1321, + 1383, 5, ], ], "start": 0, + "value": { + "value": undefined, + }, }, "env": "Server", "owner": { @@ -1540,9 +1610,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1323, + 1385, 60, - 1321, + 1383, 5, ], ], @@ -1551,9 +1621,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Child", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1317, + 1379, 28, - 1316, + 1378, 5, ], ], @@ -1601,6 +1671,8 @@ describe('ReactFlightAsyncDebugInfo', () => { stream.pipe(readable); expect(await result).toBe('HI'); + + await finishLoadingStream(readable); if ( __DEV__ && gate( @@ -1622,9 +1694,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1588, + 1658, 40, - 1572, + 1642, 57, ], ], @@ -1646,9 +1718,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1588, + 1658, 40, - 1572, + 1642, 57, ], ], @@ -1657,29 +1729,32 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 133, + 160, 12, - 132, + 159, 3, ], [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1574, + 1644, 13, - 1573, + 1643, 25, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1583, + 1653, 23, - 1582, + 1652, 5, ], ], "start": 0, + "value": { + "value": undefined, + }, }, "env": "Server", "owner": { @@ -1691,9 +1766,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1588, + 1658, 40, - 1572, + 1642, 57, ], ], @@ -1702,17 +1777,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1574, + 1644, 13, - 1573, + 1643, 25, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1583, + 1653, 23, - 1582, + 1652, 5, ], ], @@ -1732,9 +1807,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1584, + 1654, 60, - 1582, + 1652, 5, ], ], @@ -1753,9 +1828,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1588, + 1658, 40, - 1572, + 1642, 57, ], ], @@ -1764,29 +1839,32 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 133, + 160, 12, - 132, + 159, 3, ], [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1574, + 1644, 13, - 1573, + 1643, 25, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1583, + 1653, 23, - 1582, + 1652, 5, ], ], "start": 0, + "value": { + "value": undefined, + }, }, "env": "Server", }, @@ -1835,6 +1913,8 @@ describe('ReactFlightAsyncDebugInfo', () => { stream.pipe(readable); expect(await result).toBe('hi'); + + await finishLoadingStream(readable); if ( __DEV__ && gate( @@ -1856,9 +1936,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1822, + 1900, 40, - 1804, + 1882, 80, ], ], @@ -1880,9 +1960,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1822, + 1900, 40, - 1804, + 1882, 80, ], ], @@ -1891,29 +1971,32 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 133, + 160, 12, - 132, + 159, 3, ], [ "delayTrice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1812, + 1890, 13, - 1810, + 1888, 5, ], [ "Bar", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1817, + 1895, 13, - 1816, + 1894, 5, ], ], "start": 0, + "value": { + "value": undefined, + }, }, "env": "Server", "owner": { @@ -1925,9 +2008,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1822, + 1900, 40, - 1804, + 1882, 80, ], ], @@ -1936,17 +2019,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delayTrice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1812, + 1890, 13, - 1810, + 1888, 5, ], [ "Bar", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1817, + 1895, 13, - 1816, + 1894, 5, ], ], @@ -1968,9 +2051,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1822, + 1900, 40, - 1804, + 1882, 80, ], ], @@ -1979,37 +2062,40 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 133, + 160, 12, - 132, + 159, 3, ], [ "delayTwice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1806, + 1884, 13, - 1805, + 1883, 5, ], [ "delayTrice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1811, + 1889, 15, - 1810, + 1888, 5, ], [ "Bar", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1817, + 1895, 13, - 1816, + 1894, 5, ], ], "start": 0, + "value": { + "value": undefined, + }, }, "env": "Server", "owner": { @@ -2021,9 +2107,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1822, + 1900, 40, - 1804, + 1882, 80, ], ], @@ -2032,25 +2118,25 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delayTwice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1806, + 1884, 13, - 1805, + 1883, 5, ], [ "delayTrice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1811, + 1889, 15, - 1810, + 1888, 5, ], [ "Bar", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1817, + 1895, 13, - 1816, + 1894, 5, ], ], @@ -2072,9 +2158,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1822, + 1900, 40, - 1804, + 1882, 80, ], ], @@ -2083,21 +2169,24 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 133, + 160, 12, - 132, + 159, 3, ], [ "delayTwice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1807, + 1885, 13, - 1805, + 1883, 5, ], ], "start": 0, + "value": { + "value": undefined, + }, }, "env": "Server", "owner": { @@ -2109,9 +2198,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1822, + 1900, 40, - 1804, + 1882, 80, ], ], @@ -2120,9 +2209,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delayTwice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1807, + 1885, 13, - 1805, + 1883, 5, ], ], diff --git a/packages/shared/ReactTypes.js b/packages/shared/ReactTypes.js index ea9d3d1d0789a..04a30dbf8b300 100644 --- a/packages/shared/ReactTypes.js +++ b/packages/shared/ReactTypes.js @@ -234,6 +234,7 @@ export type ReactIOInfo = { +name: string, // the name of the async function being called (e.g. "fetch") +start: number, // the start time +end: number, // the end time (this might be different from the time the await was unblocked) + +value?: null | Promise, // the Promise that was awaited if any, may be rejected +env?: string, // the environment where this I/O was spawned. +owner?: null | ReactComponentInfo, +stack?: null | ReactStackTrace,