9
9
10
10
import type {
11
11
Thenable ,
12
- PendingThenable ,
13
12
FulfilledThenable ,
14
13
RejectedThenable ,
15
14
} from 'shared/ReactTypes' ;
@@ -32,111 +31,32 @@ let currentEntangledListeners: Array<() => mixed> | null = null;
32
31
let currentEntangledPendingCount : number = 0 ;
33
32
// The transition lane shared by all updates in the entangled scope.
34
33
let currentEntangledLane : Lane = NoLane ;
34
+ // A thenable that resolves when the entangled scope completes. It does not
35
+ // resolve to a particular value because it's only used for suspending the UI
36
+ // until the async action scope has completed.
37
+ let currentEntangledActionThenable : Thenable < void > | null = null ;
35
38
36
- export function requestAsyncActionContext < S > (
37
- actionReturnValue : Thenable < any > ,
38
- // If this is provided, this resulting thenable resolves to this value instead
39
- // of the return value of the action. This is a perf trick to avoid composing
40
- // an extra async function.
41
- overrideReturnValue : S | null ,
42
- ) : Thenable < S > {
43
- // This is an async action.
44
- //
45
- // Return a thenable that resolves once the action scope (i.e. the async
46
- // function passed to startTransition) has finished running.
47
-
48
- const thenable : Thenable < S > = ( actionReturnValue : any ) ;
49
- let entangledListeners ;
39
+ export function entangleAsyncAction < S > ( thenable : Thenable < S > ) : Thenable < S > {
40
+ // `thenable` is the return value of the async action scope function. Create
41
+ // a combined thenable that resolves once every entangled scope function
42
+ // has finished.
50
43
if ( currentEntangledListeners === null ) {
51
44
// There's no outer async action scope. Create a new one.
52
- entangledListeners = currentEntangledListeners = [ ] ;
45
+ const entangledListeners = ( currentEntangledListeners = [ ] ) ;
53
46
currentEntangledPendingCount = 0 ;
54
47
currentEntangledLane = requestTransitionLane ( ) ;
55
- } else {
56
- entangledListeners = currentEntangledListeners ;
48
+ const entangledThenable : Thenable < void > = {
49
+ status : 'pending' ,
50
+ value : undefined ,
51
+ then ( resolve : void => mixed ) {
52
+ entangledListeners . push ( resolve ) ;
53
+ } ,
54
+ } ;
55
+ currentEntangledActionThenable = entangledThenable ;
57
56
}
58
-
59
57
currentEntangledPendingCount ++ ;
60
-
61
- // Create a thenable that represents the result of this action, but doesn't
62
- // resolve until the entire entangled scope has finished.
63
- //
64
- // Expressed using promises:
65
- // const [thisResult] = await Promise.all([thisAction, entangledAction]);
66
- // return thisResult;
67
- const resultThenable = createResultThenable < S > ( entangledListeners ) ;
68
-
69
- let resultStatus = 'pending' ;
70
- let resultValue ;
71
- let rejectedReason ;
72
- thenable . then (
73
- ( value : S ) => {
74
- resultStatus = 'fulfilled' ;
75
- resultValue = overrideReturnValue !== null ? overrideReturnValue : value ;
76
- pingEngtangledActionScope ( ) ;
77
- } ,
78
- error => {
79
- resultStatus = 'rejected' ;
80
- rejectedReason = error ;
81
- pingEngtangledActionScope ( ) ;
82
- } ,
83
- ) ;
84
-
85
- // Attach a listener to fill in the result.
86
- entangledListeners . push ( ( ) => {
87
- switch ( resultStatus ) {
88
- case 'fulfilled' : {
89
- const fulfilledThenable : FulfilledThenable < S > = ( resultThenable : any ) ;
90
- fulfilledThenable . status = 'fulfilled' ;
91
- fulfilledThenable . value = resultValue ;
92
- break ;
93
- }
94
- case 'rejected' : {
95
- const rejectedThenable : RejectedThenable < S > = (resultThenable: any);
96
- rejectedThenable.status = 'rejected';
97
- rejectedThenable.reason = rejectedReason;
98
- break;
99
- }
100
- case 'pending ':
101
- default : {
102
- // The listener above should have been called first, so `resultStatus`
103
- // should already be set to the correct value.
104
- throw new Error (
105
- 'Thenable should have already resolved. This ' + 'is a bug in React.' ,
106
- ) ;
107
- }
108
- }
109
- } ) ;
110
-
111
- return resultThenable ;
112
- }
113
-
114
- export function requestSyncActionContext < S > (
115
- actionReturnValue: any,
116
- // If this is provided, this resulting thenable resolves to this value instead
117
- // of the return value of the action. This is a perf trick to avoid composing
118
- // an extra async function.
119
- overrideReturnValue: S | null,
120
- ): Thenable< S > | S {
121
- const resultValue : S =
122
- overrideReturnValue !== null
123
- ? overrideReturnValue
124
- : ( actionReturnValue : any ) ;
125
- // This is not an async action, but it may be part of an outer async action.
126
- if ( currentEntangledListeners === null ) {
127
- return resultValue ;
128
- } else {
129
- // Return a thenable that does not resolve until the entangled actions
130
- // have finished.
131
- const entangledListeners = currentEntangledListeners ;
132
- const resultThenable = createResultThenable < S > ( entangledListeners ) ;
133
- entangledListeners . push ( ( ) => {
134
- const fulfilledThenable : FulfilledThenable < S > = (resultThenable: any);
135
- fulfilledThenable.status = 'fulfilled';
136
- fulfilledThenable.value = resultValue;
137
- } ) ;
138
- return resultThenable ;
139
- }
58
+ thenable . then ( pingEngtangledActionScope , pingEngtangledActionScope) ;
59
+ return thenable ;
140
60
}
141
61
142
62
function pingEngtangledActionScope ( ) {
@@ -146,41 +66,74 @@ function pingEngtangledActionScope() {
146
66
) {
147
67
// All the actions have finished. Close the entangled async action scope
148
68
// and notify all the listeners.
69
+ if ( currentEntangledActionThenable !== null ) {
70
+ const fulfilledThenable : FulfilledThenable < void > =
71
+ (currentEntangledActionThenable: any);
72
+ fulfilledThenable.status = 'fulfilled';
73
+ }
149
74
const listeners = currentEntangledListeners ;
150
75
currentEntangledListeners = null ;
151
76
currentEntangledLane = NoLane ;
77
+ currentEntangledActionThenable = null ;
152
78
for ( let i = 0 ; i < listeners . length ; i ++ ) {
153
79
const listener = listeners [ i ] ;
154
80
listener ( ) ;
155
81
}
156
82
}
157
83
}
158
84
159
- function createResultThenable< S > (
160
- entangledListeners: Array< ( ) => mixed > ,
161
- ): Thenable< S > {
162
- // Waits for the entangled async action to complete, then resolves to the
163
- // result of an individual action.
164
- const resultThenable : PendingThenable < S > = {
85
+ export function chainThenableValue < T > (
86
+ thenable : Thenable < T > ,
87
+ result : T ,
88
+ ) : Thenable < T > {
89
+ // Equivalent to: Promise.resolve(thenable).then(() => result), except we can
90
+ // cheat a bit since we know that that this thenable is only ever consumed
91
+ // by React.
92
+ //
93
+ // We don't technically require promise support on the client yet, hence this
94
+ // extra code.
95
+ const listeners = [ ] ;
96
+ const thenableWithOverride : Thenable < T > = {
165
97
status : 'pending' ,
166
98
value : null ,
167
99
reason : null ,
168
- then ( resolve : S => mixed ) {
169
- // This is a bit of a cheat. `resolve` expects a value of type `S` to be
170
- // passed, but because we're instrumenting the `status` field ourselves,
171
- // and we know this thenable will only be used by React, we also know
172
- // the value isn't actually needed. So we add the resolve function
173
- // directly to the entangled listeners.
174
- //
175
- // This is also why we don't need to check if the thenable is still
176
- // pending; the Suspense implementation already performs that check.
177
- const ping : ( ) => mixed = ( resolve : any ) ;
178
- entangledListeners . push ( ping ) ;
100
+ then ( resolve : T => mixed ) {
101
+ listeners . push ( resolve ) ;
179
102
} ,
180
103
} ;
181
- return resultThenable ;
104
+ thenable . then (
105
+ ( value : T ) => {
106
+ const fulfilledThenable : FulfilledThenable < T > =
107
+ (thenableWithOverride: any);
108
+ fulfilledThenable.status = 'fulfilled';
109
+ fulfilledThenable.value = result;
110
+ for (let i = 0; i < listeners . length ; i ++ ) {
111
+ const listener = listeners [ i ] ;
112
+ listener ( result ) ;
113
+ }
114
+ } ,
115
+ error = > {
116
+ const rejectedThenable : RejectedThenable < T > = (thenableWithOverride: any);
117
+ rejectedThenable.status = 'rejected';
118
+ rejectedThenable.reason = error;
119
+ for (let i = 0; i < listeners . length ; i ++ ) {
120
+ const listener = listeners [ i ] ;
121
+ // This is a perf hack where we call the `onFulfill` ping function
122
+ // instead of `onReject`, because we know that React is the only
123
+ // consumer of these promises, and it passes the same listener to both.
124
+ // We also know that it will read the error directly off the
125
+ // `.reason` field.
126
+ listener ( ( undefined : any ) ) ;
127
+ }
128
+ } ,
129
+ ) ;
130
+ return thenableWithOverride ;
182
131
}
183
132
184
133
export function peekEntangledActionLane ( ) : Lane {
185
134
return currentEntangledLane ;
186
135
}
136
+
137
+ export function peekEntangledActionThenable ( ) : Thenable < void > | null {
138
+ return currentEntangledActionThenable ;
139
+ }
0 commit comments