99
1010import type {
1111 Thenable ,
12- PendingThenable ,
1312 FulfilledThenable ,
1413 RejectedThenable ,
1514} from 'shared/ReactTypes' ;
@@ -32,111 +31,32 @@ let currentEntangledListeners: Array<() => mixed> | null = null;
3231let currentEntangledPendingCount : number = 0 ;
3332// The transition lane shared by all updates in the entangled scope.
3433let 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 ;
3538
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.
5043 if ( currentEntangledListeners === null ) {
5144 // There's no outer async action scope. Create a new one.
52- entangledListeners = currentEntangledListeners = [ ] ;
45+ const entangledListeners = ( currentEntangledListeners = [ ] ) ;
5346 currentEntangledPendingCount = 0 ;
5447 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 ;
5756 }
58-
5957 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 ;
14060}
14161
14262function pingEngtangledActionScope ( ) {
@@ -146,41 +66,74 @@ function pingEngtangledActionScope() {
14666 ) {
14767 // All the actions have finished. Close the entangled async action scope
14868 // and notify all the listeners.
69+ if ( currentEntangledActionThenable !== null ) {
70+ const fulfilledThenable : FulfilledThenable < void > =
71+ (currentEntangledActionThenable: any);
72+ fulfilledThenable.status = 'fulfilled';
73+ }
14974 const listeners = currentEntangledListeners ;
15075 currentEntangledListeners = null ;
15176 currentEntangledLane = NoLane ;
77+ currentEntangledActionThenable = null ;
15278 for ( let i = 0 ; i < listeners . length ; i ++ ) {
15379 const listener = listeners [ i ] ;
15480 listener ( ) ;
15581 }
15682 }
15783}
15884
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 > = {
16597 status : 'pending' ,
16698 value : null ,
16799 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 ) ;
179102 } ,
180103 } ;
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 ;
182131}
183132
184133export function peekEntangledActionLane ( ) : Lane {
185134 return currentEntangledLane ;
186135}
136+
137+ export function peekEntangledActionThenable ( ) : Thenable < void > | null {
138+ return currentEntangledActionThenable ;
139+ }
0 commit comments