@@ -17,8 +17,7 @@ import type {
17
17
import ReactSharedInternals from 'shared/ReactSharedInternals' ;
18
18
const { ReactCurrentActQueue} = ReactSharedInternals ;
19
19
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 >> ;
22
21
23
22
let thenableState : ThenableState | null = null ;
24
23
@@ -62,15 +61,30 @@ export function isThenableStateResolved(thenables: ThenableState): boolean {
62
61
return true ;
63
62
}
64
63
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 {
66
67
if ( __DEV__ && ReactCurrentActQueue . current !== null ) {
67
68
ReactCurrentActQueue . didUsePromise = true ;
68
69
}
69
70
70
71
if ( thenableState === null ) {
71
72
thenableState = [ thenable ] ;
72
73
} 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
+ }
74
88
}
75
89
76
90
// 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) {
80
94
// If the thenable doesn't have a status, set it to "pending" and attach
81
95
// a listener that will update its status and result when it resolves.
82
96
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
+ }
91
105
default : {
92
106
if ( typeof thenable . status === 'string' ) {
93
107
// Only instrument the thenable if the status if not defined. If
94
108
// it's defined, but an unknown value, assume it's been instrumented by
95
109
// 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
+ ) ;
97
129
}
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
- }
120
130
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 ;
128
139
}
129
140
}
130
- return null;
131
141
}
0 commit comments