@@ -216,6 +216,82 @@ const POP = {};
216216const jsxPropsParents : WeakMap < any , any > = new WeakMap ( ) ;
217217const jsxChildrenParents : WeakMap < any , any > = new WeakMap ( ) ;
218218
219+ function serializeThenable ( request : Request , thenable : Thenable < any > ) : number {
220+ request . pendingChunks ++ ;
221+ const newTask = createTask (
222+ request ,
223+ null ,
224+ getActiveContext ( ) ,
225+ request . abortableTasks ,
226+ ) ;
227+
228+ switch ( thenable . status ) {
229+ case 'fulfilled' : {
230+ // We have the resolved value, we can go ahead and schedule it for serialization.
231+ newTask. model = thenable . value ;
232+ pingTask ( request , newTask ) ;
233+ return newTask . id ;
234+ }
235+ case 'rejected' : {
236+ const x = thenable . reason ;
237+ const digest = logRecoverableError ( request , x ) ;
238+ if ( __DEV__ ) {
239+ const { message, stack} = getErrorMessageAndStackDev ( x ) ;
240+ emitErrorChunkDev ( request , newTask . id , digest , message , stack ) ;
241+ } else {
242+ emitErrorChunkProd ( request , newTask . id , digest ) ;
243+ }
244+ return newTask . id ;
245+ }
246+ default : {
247+ if ( typeof thenable . status === 'string' ) {
248+ // Only instrument the thenable if the status if not defined. If
249+ // it's defined, but an unknown value, assume it's been instrumented by
250+ // some custom userspace implementation. We treat it as "pending".
251+ break ;
252+ }
253+ const pendingThenable : PendingThenable < mixed > = ( thenable : any ) ;
254+ pendingThenable . status = 'pending' ;
255+ pendingThenable . then (
256+ fulfilledValue => {
257+ if ( thenable . status === 'pending' ) {
258+ const fulfilledThenable : FulfilledThenable < mixed > = (thenable: any);
259+ fulfilledThenable.status = 'fulfilled';
260+ fulfilledThenable.value = fulfilledValue;
261+ }
262+ } ,
263+ ( error : mixed ) => {
264+ if ( thenable . status === 'pending' ) {
265+ const rejectedThenable : RejectedThenable < mixed > = (thenable: any);
266+ rejectedThenable.status = 'rejected';
267+ rejectedThenable.reason = error;
268+ }
269+ } ,
270+ ) ;
271+ break ;
272+ }
273+ }
274+
275+ thenable . then (
276+ value => {
277+ newTask . model = value ;
278+ pingTask ( request , newTask ) ;
279+ } ,
280+ reason => {
281+ // TODO: Is it safe to directly emit these without being inside a retry?
282+ const digest = logRecoverableError ( request , reason ) ;
283+ if ( __DEV__ ) {
284+ const { message, stack} = getErrorMessageAndStackDev ( reason ) ;
285+ emitErrorChunkDev ( request , newTask . id , digest , message , stack ) ;
286+ } else {
287+ emitErrorChunkProd ( request , newTask . id , digest ) ;
288+ }
289+ } ,
290+ ) ;
291+
292+ return newTask . id ;
293+ }
294+
219295function readThenable < T > ( thenable : Thenable < T > ) : T {
220296 if ( thenable . status === 'fulfilled' ) {
221297 return thenable . value ;
@@ -270,6 +346,7 @@ function createLazyWrapperAroundWakeable(wakeable: Wakeable) {
270346}
271347
272348function attemptResolveElement (
349+ request : Request ,
273350 type : any ,
274351 key : null | React$Key ,
275352 ref : mixed ,
@@ -303,6 +380,14 @@ function attemptResolveElement(
303380 result !== null &&
304381 typeof result . then === 'function'
305382 ) {
383+ // When the return value is in children position we can resolve it immediately,
384+ // to its value without a wrapper if it's synchronously available.
385+ const thenable : Thenable < any > = result;
386+ if (thenable.status === 'fulfilled') {
387+ return thenable . value ;
388+ }
389+ // TODO: Once we accept Promises as children on the client, we can just return
390+ // the thenable here.
306391 return createLazyWrapperAroundWakeable(result);
307392 }
308393 return result ;
@@ -331,6 +416,7 @@ function attemptResolveElement(
331416 const init = type . _init ;
332417 const wrappedType = init ( payload ) ;
333418 return attemptResolveElement (
419+ request ,
334420 wrappedType ,
335421 key ,
336422 ref ,
@@ -345,6 +431,7 @@ function attemptResolveElement(
345431 }
346432 case REACT_MEMO_TYPE : {
347433 return attemptResolveElement (
434+ request ,
348435 type . type ,
349436 key ,
350437 ref ,
@@ -414,10 +501,14 @@ function serializeByValueID(id: number): string {
414501 return '$ ' + id . toString ( 16 ) ;
415502}
416503
417- function serializeByRefID ( id : number ) : string {
504+ function serializeLazyID ( id : number ) : string {
418505 return '$L' + id . toString ( 16 ) ;
419506}
420507
508+ function serializePromiseID ( id : number ) : string {
509+ return '$@' + id . toString ( 16 ) ;
510+ }
511+
421512function serializeSymbolReference ( name : string ) : string {
422513 return '$S' + name ;
423514}
@@ -442,7 +533,7 @@ function serializeClientReference(
442533 // knows how to deal with lazy values. This lets us suspend
443534 // on this component rather than its parent until the code has
444535 // loaded.
445- return serializeByRefID ( existingId ) ;
536+ return serializeLazyID ( existingId ) ;
446537 }
447538 return serializeByValueID ( existingId ) ;
448539 }
@@ -461,7 +552,7 @@ function serializeClientReference(
461552 // knows how to deal with lazy values. This lets us suspend
462553 // on this component rather than its parent until the code has
463554 // loaded.
464- return serializeByRefID ( moduleId ) ;
555+ return serializeLazyID ( moduleId ) ;
465556 }
466557 return serializeByValueID ( moduleId ) ;
467558 } catch ( x ) {
@@ -835,6 +926,7 @@ export function resolveModelToJSON(
835926 const element : React$Element < any > = ( value : any ) ;
836927 // Attempt to render the Server Component.
837928 value = attemptResolveElement (
929+ request ,
838930 element . type ,
839931 element . key ,
840932 element . ref ,
@@ -873,7 +965,7 @@ export function resolveModelToJSON(
873965 const ping = newTask . ping ;
874966 x . then ( ping , ping ) ;
875967 newTask . thenableState = getThenableStateAfterSuspending ( ) ;
876- return serializeByRefID ( newTask . id ) ;
968+ return serializeLazyID ( newTask . id ) ;
877969 } else {
878970 // Something errored. We'll still send everything we have up until this point.
879971 // We'll replace this element with a lazy reference that throws on the client
@@ -887,7 +979,7 @@ export function resolveModelToJSON(
887979 } else {
888980 emitErrorChunkProd ( request , errorId , digest ) ;
889981 }
890- return serializeByRefID ( errorId ) ;
982+ return serializeLazyID ( errorId ) ;
891983 }
892984 }
893985 }
@@ -899,6 +991,11 @@ export function resolveModelToJSON(
899991 if ( typeof value === 'object' ) {
900992 if ( isClientReference ( value ) ) {
901993 return serializeClientReference ( request , parent , key , ( value : any ) ) ;
994+ } else if ( typeof value . then === 'function' ) {
995+ // We assume that any object with a .then property is a "Thenable" type,
996+ // or a Promise type. Either of which can be represented by a Promise.
997+ const promiseId = serializeThenable ( request , ( value : any ) ) ;
998+ return serializePromiseID ( promiseId ) ;
902999 } else if ( ( value : any ) . $$typeof === REACT_PROVIDER_TYPE ) {
9031000 const providerKey = ( ( value : any ) : ReactProviderType < any > ) . _context
9041001 . _globalName ;
@@ -1157,6 +1254,7 @@ function retryTask(request: Request, task: Task): void {
11571254 // also suspends.
11581255 task . model = value ;
11591256 value = attemptResolveElement (
1257+ request ,
11601258 element . type ,
11611259 element . key ,
11621260 element . ref ,
@@ -1180,6 +1278,7 @@ function retryTask(request: Request, task: Task): void {
11801278 const nextElement : React$Element < any > = ( value : any ) ;
11811279 task . model = value ;
11821280 value = attemptResolveElement (
1281+ request ,
11831282 nextElement . type ,
11841283 nextElement . key ,
11851284 nextElement . ref ,
0 commit comments