Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit fa77f52

Browse files
committed
Unify promise switch statements
There are two different switch statements that we use to unwrap a `use`-ed promise, but there really only needs to be one. This was a factoring artifact that arose because I implemented the yieldy `status` instrumentation thing before I implemented `use` (for promises that are thrown directly during render, which is the old Suspense pattern that will be superseded by `use`).
1 parent 7572e49 commit fa77f52

File tree

8 files changed

+222
-407
lines changed

8 files changed

+222
-407
lines changed

packages/react-reconciler/src/ReactFiberHooks.new.js

Lines changed: 1 addition & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,6 @@ import {now} from './Scheduler';
137137
import {
138138
prepareThenableState,
139139
trackUsedThenable,
140-
getPreviouslyUsedThenableAtIndex,
141140
} from './ReactFiberThenable.new';
142141

143142
const {ReactCurrentDispatcher, ReactCurrentBatchConfig} = ReactSharedInternals;
@@ -776,8 +775,6 @@ if (enableUseMemoCacheHook) {
776775
};
777776
}
778777

779-
function noop(): void {}
780-
781778
function use<T>(usable: Usable<T>): T {
782779
if (usable !== null && typeof usable === 'object') {
783780
// $FlowFixMe[method-unbinding]
@@ -788,59 +785,7 @@ function use<T>(usable: Usable<T>): T {
788785
// Track the position of the thenable within this fiber.
789786
const index = thenableIndexCounter;
790787
thenableIndexCounter += 1;
791-
792-
// TODO: Unify this switch statement with the one in trackUsedThenable.
793-
switch (thenable.status) {
794-
case 'fulfilled': {
795-
const fulfilledValue: T = thenable.value;
796-
return fulfilledValue;
797-
}
798-
case 'rejected': {
799-
const rejectedError = thenable.reason;
800-
throw rejectedError;
801-
}
802-
default: {
803-
const prevThenableAtIndex: Thenable<T> | null = getPreviouslyUsedThenableAtIndex(
804-
index,
805-
);
806-
if (prevThenableAtIndex !== null) {
807-
if (thenable !== prevThenableAtIndex) {
808-
// Avoid an unhandled rejection errors for the Promises that we'll
809-
// intentionally ignore.
810-
thenable.then(noop, noop);
811-
}
812-
switch (prevThenableAtIndex.status) {
813-
case 'fulfilled': {
814-
const fulfilledValue: T = prevThenableAtIndex.value;
815-
return fulfilledValue;
816-
}
817-
case 'rejected': {
818-
const rejectedError: mixed = prevThenableAtIndex.reason;
819-
throw rejectedError;
820-
}
821-
default: {
822-
// The thenable still hasn't resolved. Suspend with the same
823-
// thenable as last time to avoid redundant listeners.
824-
throw prevThenableAtIndex;
825-
}
826-
}
827-
} else {
828-
// This is the first time something has been used at this index.
829-
// Stash the thenable at the current index so we can reuse it during
830-
// the next attempt.
831-
trackUsedThenable(thenable, index);
832-
833-
// Suspend.
834-
// TODO: Throwing here is an implementation detail that allows us to
835-
// unwind the call stack. But we shouldn't allow it to leak into
836-
// userspace. Throw an opaque placeholder value instead of the
837-
// actual thenable. If it doesn't get captured by the work loop, log
838-
// a warning, because that means something in userspace must have
839-
// caught it.
840-
throw thenable;
841-
}
842-
}
843-
}
788+
return trackUsedThenable(thenable, index);
844789
} else if (
845790
usable.$$typeof === REACT_CONTEXT_TYPE ||
846791
usable.$$typeof === REACT_SERVER_CONTEXT_TYPE

packages/react-reconciler/src/ReactFiberHooks.old.js

Lines changed: 1 addition & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,6 @@ import {now} from './Scheduler';
137137
import {
138138
prepareThenableState,
139139
trackUsedThenable,
140-
getPreviouslyUsedThenableAtIndex,
141140
} from './ReactFiberThenable.old';
142141

143142
const {ReactCurrentDispatcher, ReactCurrentBatchConfig} = ReactSharedInternals;
@@ -776,8 +775,6 @@ if (enableUseMemoCacheHook) {
776775
};
777776
}
778777

779-
function noop(): void {}
780-
781778
function use<T>(usable: Usable<T>): T {
782779
if (usable !== null && typeof usable === 'object') {
783780
// $FlowFixMe[method-unbinding]
@@ -788,59 +785,7 @@ function use<T>(usable: Usable<T>): T {
788785
// Track the position of the thenable within this fiber.
789786
const index = thenableIndexCounter;
790787
thenableIndexCounter += 1;
791-
792-
// TODO: Unify this switch statement with the one in trackUsedThenable.
793-
switch (thenable.status) {
794-
case 'fulfilled': {
795-
const fulfilledValue: T = thenable.value;
796-
return fulfilledValue;
797-
}
798-
case 'rejected': {
799-
const rejectedError = thenable.reason;
800-
throw rejectedError;
801-
}
802-
default: {
803-
const prevThenableAtIndex: Thenable<T> | null = getPreviouslyUsedThenableAtIndex(
804-
index,
805-
);
806-
if (prevThenableAtIndex !== null) {
807-
if (thenable !== prevThenableAtIndex) {
808-
// Avoid an unhandled rejection errors for the Promises that we'll
809-
// intentionally ignore.
810-
thenable.then(noop, noop);
811-
}
812-
switch (prevThenableAtIndex.status) {
813-
case 'fulfilled': {
814-
const fulfilledValue: T = prevThenableAtIndex.value;
815-
return fulfilledValue;
816-
}
817-
case 'rejected': {
818-
const rejectedError: mixed = prevThenableAtIndex.reason;
819-
throw rejectedError;
820-
}
821-
default: {
822-
// The thenable still hasn't resolved. Suspend with the same
823-
// thenable as last time to avoid redundant listeners.
824-
throw prevThenableAtIndex;
825-
}
826-
}
827-
} else {
828-
// This is the first time something has been used at this index.
829-
// Stash the thenable at the current index so we can reuse it during
830-
// the next attempt.
831-
trackUsedThenable(thenable, index);
832-
833-
// Suspend.
834-
// TODO: Throwing here is an implementation detail that allows us to
835-
// unwind the call stack. But we shouldn't allow it to leak into
836-
// userspace. Throw an opaque placeholder value instead of the
837-
// actual thenable. If it doesn't get captured by the work loop, log
838-
// a warning, because that means something in userspace must have
839-
// caught it.
840-
throw thenable;
841-
}
842-
}
843-
}
788+
return trackUsedThenable(thenable, index);
844789
} else if (
845790
usable.$$typeof === REACT_CONTEXT_TYPE ||
846791
usable.$$typeof === REACT_SERVER_CONTEXT_TYPE

packages/react-reconciler/src/ReactFiberThenable.new.js

Lines changed: 53 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ import type {
1717
import ReactSharedInternals from 'shared/ReactSharedInternals';
1818
const {ReactCurrentActQueue} = ReactSharedInternals;
1919

20-
// TODO: Sparse arrays are bad for performance.
21-
export opaque type ThenableState = Array<Thenable<any> | void>;
20+
export opaque type ThenableState = Array<Thenable<any>>;
2221

2322
let thenableState: ThenableState | null = null;
2423

@@ -62,15 +61,30 @@ export function isThenableStateResolved(thenables: ThenableState): boolean {
6261
return true;
6362
}
6463

65-
export function trackUsedThenable<T>(thenable: Thenable<T>, index: number) {
64+
function noop(): void {}
65+
66+
export function trackUsedThenable<T>(thenable: Thenable<T>, index: number): T {
6667
if (__DEV__ && ReactCurrentActQueue.current !== null) {
6768
ReactCurrentActQueue.didUsePromise = true;
6869
}
6970

7071
if (thenableState === null) {
7172
thenableState = [thenable];
7273
} else {
73-
thenableState[index] = thenable;
74+
const previous = thenableState[index];
75+
if (previous === undefined) {
76+
thenableState.push(thenable);
77+
} else {
78+
if (previous !== thenable) {
79+
// Reuse the previous thenable, and drop the new one. We can assume
80+
// they represent the same value, because components are idempotent.
81+
82+
// Avoid an unhandled rejection errors for the Promises that we'll
83+
// intentionally ignore.
84+
thenable.then(noop, noop);
85+
thenable = previous;
86+
}
87+
}
7488
}
7589

7690
// We use an expando to track the status and result of a thenable so that we
@@ -80,52 +94,48 @@ export function trackUsedThenable<T>(thenable: Thenable<T>, index: number) {
8094
// If the thenable doesn't have a status, set it to "pending" and attach
8195
// a listener that will update its status and result when it resolves.
8296
switch (thenable.status) {
83-
case 'fulfilled':
84-
case 'rejected':
85-
// A thenable that already resolved shouldn't have been thrown, so this is
86-
// unexpected. Suggests a mistake in a userspace data library. Don't track
87-
// this thenable, because if we keep trying it will likely infinite loop
88-
// without ever resolving.
89-
// TODO: Log a warning?
90-
break;
97+
case 'fulfilled': {
98+
const fulfilledValue: T = thenable.value;
99+
return fulfilledValue;
100+
}
101+
case 'rejected': {
102+
const rejectedError = thenable.reason;
103+
throw rejectedError;
104+
}
91105
default: {
92106
if (typeof thenable.status === 'string') {
93107
// Only instrument the thenable if the status if not defined. If
94108
// it's defined, but an unknown value, assume it's been instrumented by
95109
// some custom userspace implementation. We treat it as "pending".
96-
break;
110+
} else {
111+
const pendingThenable: PendingThenable<mixed> = (thenable: any);
112+
pendingThenable.status = 'pending';
113+
pendingThenable.then(
114+
fulfilledValue => {
115+
if (thenable.status === 'pending') {
116+
const fulfilledThenable: FulfilledThenable<mixed> = (thenable: any);
117+
fulfilledThenable.status = 'fulfilled';
118+
fulfilledThenable.value = fulfilledValue;
119+
}
120+
},
121+
(error: mixed) => {
122+
if (thenable.status === 'pending') {
123+
const rejectedThenable: RejectedThenable<mixed> = (thenable: any);
124+
rejectedThenable.status = 'rejected';
125+
rejectedThenable.reason = error;
126+
}
127+
},
128+
);
97129
}
98-
const pendingThenable: PendingThenable<mixed> = (thenable: any);
99-
pendingThenable.status = 'pending';
100-
pendingThenable.then(
101-
fulfilledValue => {
102-
if (thenable.status === 'pending') {
103-
const fulfilledThenable: FulfilledThenable<mixed> = (thenable: any);
104-
fulfilledThenable.status = 'fulfilled';
105-
fulfilledThenable.value = fulfilledValue;
106-
}
107-
},
108-
(error: mixed) => {
109-
if (thenable.status === 'pending') {
110-
const rejectedThenable: RejectedThenable<mixed> = (thenable: any);
111-
rejectedThenable.status = 'rejected';
112-
rejectedThenable.reason = error;
113-
}
114-
},
115-
);
116-
break;
117-
}
118-
}
119-
}
120130

121-
export function getPreviouslyUsedThenableAtIndex<T>(
122-
index: number,
123-
): Thenable<T> | null {
124-
if (thenableState !== null) {
125-
const thenable = thenableState[index];
126-
if (thenable !== undefined) {
127-
return thenable;
131+
// Suspend.
132+
// TODO: Throwing here is an implementation detail that allows us to
133+
// unwind the call stack. But we shouldn't allow it to leak into
134+
// userspace. Throw an opaque placeholder value instead of the
135+
// actual thenable. If it doesn't get captured by the work loop, log
136+
// a warning, because that means something in userspace must have
137+
// caught it.
138+
throw thenable;
128139
}
129140
}
130-
return null;
131141
}

0 commit comments

Comments
 (0)