mirror of
https://github.com/facebook/react.git
synced 2026-02-24 04:33:04 +00:00
[Flight] Optimize Retention of Weak Promises Abit (#33736)
We don't really need to retain a reference to whatever Promise another Promise was created in. Only awaits need to retain both their trigger and their previous context.
This commit is contained in:
committed by
GitHub
parent
3a43e72d66
commit
49ded1d12a
@@ -45,14 +45,10 @@ const pendingOperations: Map<number, AsyncSequence> =
|
||||
// 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<any>, Promise<any>> = __DEV__ &&
|
||||
enableAsyncDebugInfo
|
||||
? new WeakMap()
|
||||
: (null: any);
|
||||
const previousPromise: WeakMap<Promise<any>, Promise<any>> = __DEV__ &&
|
||||
enableAsyncDebugInfo
|
||||
? new WeakMap()
|
||||
: (null: any);
|
||||
const awaitedPromise: WeakMap<
|
||||
Promise<any>,
|
||||
Promise<any> | [Promise<any>, Promise<any>],
|
||||
> = __DEV__ && enableAsyncDebugInfo ? new WeakMap() : (null: any);
|
||||
|
||||
// Keep the last resolved await as a workaround for async functions missing data.
|
||||
let lastRanAwait: null | AwaitNode = null;
|
||||
@@ -88,14 +84,6 @@ 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,
|
||||
@@ -104,18 +92,42 @@ export function initAsyncDebugInfo(): void {
|
||||
// We don't track awaits on things that started outside our tracked scope.
|
||||
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
|
||||
let retain: null | Promise<any> | [Promise<any>, Promise<any>] =
|
||||
null;
|
||||
const triggerPromiseRef = trigger.promise;
|
||||
if (triggerPromiseRef !== null) {
|
||||
const triggerPromise = triggerPromiseRef.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.
|
||||
previousPromise.set(resource, currentPromise);
|
||||
retain = triggerPromise;
|
||||
}
|
||||
}
|
||||
|
||||
const current = pendingOperations.get(currentAsyncId);
|
||||
if (current !== undefined) {
|
||||
const currentPromiseRef = current.promise;
|
||||
if (currentPromiseRef !== null) {
|
||||
const currentPromise = currentPromiseRef.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.
|
||||
if (retain === null) {
|
||||
retain = currentPromise;
|
||||
} else {
|
||||
retain = [(retain: any), currentPromise];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (retain !== null) {
|
||||
awaitedPromise.set(resource, retain);
|
||||
}
|
||||
// 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.
|
||||
let stack = null;
|
||||
let promiseRef: WeakRef<Promise<any>>;
|
||||
if (
|
||||
trigger.stack !== null &&
|
||||
(trigger.tag === AWAIT_NODE ||
|
||||
@@ -124,7 +136,15 @@ export function initAsyncDebugInfo(): void {
|
||||
// We already had a stack for an await. In a chain of awaits we'll only need one good stack.
|
||||
// We mark it with an empty stack to signal to any await on this await that we have a stack.
|
||||
stack = emptyStack;
|
||||
if (resource._debugInfo !== undefined) {
|
||||
// We may need to forward this debug info at the end so we need to retain this promise.
|
||||
promiseRef = new WeakRef((resource: Promise<any>));
|
||||
} else {
|
||||
// Otherwise, we can just refer to the inner one since that's the one we'll log anyway.
|
||||
promiseRef = trigger.promise;
|
||||
}
|
||||
} else {
|
||||
promiseRef = new WeakRef((resource: Promise<any>));
|
||||
const request = resolveRequest();
|
||||
if (request === null) {
|
||||
// We don't collect stacks for awaits that weren't in the scope of a specific render.
|
||||
@@ -144,7 +164,7 @@ export function initAsyncDebugInfo(): void {
|
||||
stack: stack,
|
||||
start: performance.now(),
|
||||
end: -1.1, // set when resolved.
|
||||
promise: new WeakRef((resource: Promise<any>)),
|
||||
promise: promiseRef,
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user