@@ -216,6 +216,82 @@ const POP = {};
216
216
const jsxPropsParents : WeakMap < any , any > = new WeakMap ( ) ;
217
217
const jsxChildrenParents : WeakMap < any , any > = new WeakMap ( ) ;
218
218
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
+
219
295
function readThenable < T > ( thenable : Thenable < T > ) : T {
220
296
if ( thenable . status === 'fulfilled' ) {
221
297
return thenable . value ;
@@ -270,6 +346,7 @@ function createLazyWrapperAroundWakeable(wakeable: Wakeable) {
270
346
}
271
347
272
348
function attemptResolveElement (
349
+ request : Request ,
273
350
type : any ,
274
351
key : null | React$Key ,
275
352
ref : mixed ,
@@ -303,6 +380,14 @@ function attemptResolveElement(
303
380
result !== null &&
304
381
typeof result . then === 'function'
305
382
) {
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.
306
391
return createLazyWrapperAroundWakeable(result);
307
392
}
308
393
return result ;
@@ -331,6 +416,7 @@ function attemptResolveElement(
331
416
const init = type . _init ;
332
417
const wrappedType = init ( payload ) ;
333
418
return attemptResolveElement (
419
+ request ,
334
420
wrappedType ,
335
421
key ,
336
422
ref ,
@@ -345,6 +431,7 @@ function attemptResolveElement(
345
431
}
346
432
case REACT_MEMO_TYPE : {
347
433
return attemptResolveElement (
434
+ request ,
348
435
type . type ,
349
436
key ,
350
437
ref ,
@@ -414,10 +501,14 @@ function serializeByValueID(id: number): string {
414
501
return '$ ' + id . toString ( 16 ) ;
415
502
}
416
503
417
- function serializeByRefID ( id : number ) : string {
504
+ function serializeLazyID ( id : number ) : string {
418
505
return '$L' + id . toString ( 16 ) ;
419
506
}
420
507
508
+ function serializePromiseID ( id : number ) : string {
509
+ return '$@' + id . toString ( 16 ) ;
510
+ }
511
+
421
512
function serializeSymbolReference ( name : string ) : string {
422
513
return '$S' + name ;
423
514
}
@@ -442,7 +533,7 @@ function serializeClientReference(
442
533
// knows how to deal with lazy values. This lets us suspend
443
534
// on this component rather than its parent until the code has
444
535
// loaded.
445
- return serializeByRefID ( existingId ) ;
536
+ return serializeLazyID ( existingId ) ;
446
537
}
447
538
return serializeByValueID ( existingId ) ;
448
539
}
@@ -461,7 +552,7 @@ function serializeClientReference(
461
552
// knows how to deal with lazy values. This lets us suspend
462
553
// on this component rather than its parent until the code has
463
554
// loaded.
464
- return serializeByRefID ( moduleId ) ;
555
+ return serializeLazyID ( moduleId ) ;
465
556
}
466
557
return serializeByValueID ( moduleId ) ;
467
558
} catch ( x ) {
@@ -835,6 +926,7 @@ export function resolveModelToJSON(
835
926
const element : React$Element < any > = ( value : any ) ;
836
927
// Attempt to render the Server Component.
837
928
value = attemptResolveElement (
929
+ request ,
838
930
element . type ,
839
931
element . key ,
840
932
element . ref ,
@@ -873,7 +965,7 @@ export function resolveModelToJSON(
873
965
const ping = newTask . ping ;
874
966
x . then ( ping , ping ) ;
875
967
newTask . thenableState = getThenableStateAfterSuspending ( ) ;
876
- return serializeByRefID ( newTask . id ) ;
968
+ return serializeLazyID ( newTask . id ) ;
877
969
} else {
878
970
// Something errored. We'll still send everything we have up until this point.
879
971
// We'll replace this element with a lazy reference that throws on the client
@@ -887,7 +979,7 @@ export function resolveModelToJSON(
887
979
} else {
888
980
emitErrorChunkProd ( request , errorId , digest ) ;
889
981
}
890
- return serializeByRefID ( errorId ) ;
982
+ return serializeLazyID ( errorId ) ;
891
983
}
892
984
}
893
985
}
@@ -899,6 +991,11 @@ export function resolveModelToJSON(
899
991
if ( typeof value === 'object' ) {
900
992
if ( isClientReference ( value ) ) {
901
993
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 ) ;
902
999
} else if ( ( value : any ) . $$typeof === REACT_PROVIDER_TYPE ) {
903
1000
const providerKey = ( ( value : any ) : ReactProviderType < any > ) . _context
904
1001
. _globalName ;
@@ -1157,6 +1254,7 @@ function retryTask(request: Request, task: Task): void {
1157
1254
// also suspends.
1158
1255
task . model = value ;
1159
1256
value = attemptResolveElement (
1257
+ request ,
1160
1258
element . type ,
1161
1259
element . key ,
1162
1260
element . ref ,
@@ -1180,6 +1278,7 @@ function retryTask(request: Request, task: Task): void {
1180
1278
const nextElement : React$Element < any > = ( value : any ) ;
1181
1279
task . model = value ;
1182
1280
value = attemptResolveElement (
1281
+ request ,
1183
1282
nextElement . type ,
1184
1283
nextElement . key ,
1185
1284
nextElement . ref ,
0 commit comments