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,