165
166
type PendingChunk<T> = {
167
status: 'pending',
168
- value: null | Array<(T) => mixed>,
169
- reason: null | Array<(mixed) => mixed>,
168
+ value: null | Array<InitializationReference | (T => mixed)>,
169
+ reason: null | Array<InitializationReference | (mixed => mixed)>,
170
_children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
171
_debugInfo?: null | ReactDebugInfo, // DEV-only
172
then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
173
};
174
type BlockedChunk<T> = {
175
status: 'blocked',
176
- value: null | Array<(T) => mixed>,
177
- reason: null | Array<(mixed) => mixed>,
176
+ value: null | Array<InitializationReference | (T => mixed)>,
177
+ reason: null | Array<InitializationReference | (mixed => mixed)>,
178
_children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
179
_debugInfo?: null | ReactDebugInfo, // DEV-only
180
then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
269
initializeModuleChunk(chunk);
270
break;
271
}
272
- if (
273
- __DEV__ &&
274
- enableAsyncDebugInfo &&
275
- (typeof resolve !== 'function' || !(resolve: any).isReactInternalListener)
276
- ) {
272
+ if (__DEV__ && enableAsyncDebugInfo) {
273
// Because only native Promises get picked up when we're awaiting we need to wrap
274
// this in a native Promise in DEV. This means that these callbacks are no longer sync
275
// but the lazy initialization is still sync and the .value can be inspected after,
293
// The status might have changed after initialization.
294
switch (chunk.status) {
295
case INITIALIZED:
300
- resolve(chunk.value);
296
+ if (typeof resolve === 'function') {
297
+ resolve(chunk.value);
298
+ }
299
break;
300
case PENDING:
301
case BLOCKED:
304
- if (resolve) {
302
+ if (typeof resolve === 'function') {
303
if (chunk.value === null) {
306
- chunk.value = ([]: Array<(T) => mixed>);
304
+ chunk.value = ([]: Array<InitializationReference | (T => mixed)>);
305
}
306
chunk.value.push(resolve);
307
}
310
- if (reject) {
308
+ if (typeof reject === 'function') {
309
if (chunk.reason === null) {
312
- chunk.reason = ([]: Array<(mixed) => mixed>);
310
+ chunk.reason = ([]: Array<
311
+ InitializationReference | (mixed => mixed),
312
+ >);
313
}
314
chunk.reason.push(reject);
315
}
318
break;
319
}
320
default:
321
- if (reject) {
321
+ if (typeof reject === 'function') {
322
reject(chunk.reason);
323
}
324
break;
408
return new ReactPromise(ERRORED, null, error);
409
}
410
411
-function wakeChunk<T>(listeners: Array<(T) => mixed>, value: T): void {
411
+function wakeChunk<T>(
412
+ listeners: Array<InitializationReference | (T => mixed)>,
413
+ value: T,
414
+): void {
415
for (let i = 0; i < listeners.length; i++) {
416
const listener = listeners[i];
414
- listener(value);
417
+ if (typeof listener === 'function') {
418
+ listener(value);
419
+ } else {
420
+ fulfillReference(listener, value);
421
+ }
422
}
423
}
424
425
+function rejectChunk(
426
+ listeners: Array<InitializationReference | (mixed => mixed)>,
427
+ error: mixed,
428
+): void {
429
+ for (let i = 0; i < listeners.length; i++) {
430
+ const listener = listeners[i];
431
+ if (typeof listener === 'function') {
432
+ listener(error);
433
+ } else {
434
+ rejectReference(listener, error);
435
+ }
436
+ }
437
+}
438
+
439
+function resolveBlockedCycle<T>(
440
+ resolvedChunk: SomeChunk<T>,
441
+ reference: InitializationReference,
442
+): null | InitializationHandler {
443
+ const referencedChunk = reference.handler.chunk;
444
+ if (referencedChunk === null) {
445
+ return null;
446
+ }
447
+ if (referencedChunk === resolvedChunk) {
448
+ // We found the cycle. We can resolve the blocked cycle now.
449
+ return reference.handler;
450
+ }
451
+ const resolveListeners = referencedChunk.value;
452
+ if (resolveListeners !== null) {
453
+ for (let i = 0; i < resolveListeners.length; i++) {
454
+ const listener = resolveListeners[i];
455
+ if (typeof listener !== 'function') {
456
+ const foundHandler = resolveBlockedCycle(resolvedChunk, listener);
457
+ if (foundHandler !== null) {
458
+ return foundHandler;
459
+ }
460
+ }
461
+ }
462
+ }
463
+ return null;
464
+}
465
+
466
function wakeChunkIfInitialized<T>(
467
chunk: SomeChunk<T>,
420
- resolveListeners: Array<(T) => mixed>,
421
- rejectListeners: null | Array<(mixed) => mixed>,
468
+ resolveListeners: Array<InitializationReference | (T => mixed)>,
469
+ rejectListeners: null | Array<InitializationReference | (mixed => mixed)>,
470
): void {
471
switch (chunk.status) {
472
case INITIALIZED:
473
wakeChunk(resolveListeners, chunk.value);
474
break;
427
- case PENDING:
475
case BLOCKED:
476
+ // It is possible that we're blocked on our own chunk if it's a cycle.
477
+ // Before adding back the listeners to the chunk, let's check if it would
478
+ // result in a cycle.
479
+ for (let i = 0; i < resolveListeners.length; i++) {
480
+ const listener = resolveListeners[i];
481
+ if (typeof listener !== 'function') {
482
+ const reference: InitializationReference = listener;
483
+ const cyclicHandler = resolveBlockedCycle(chunk, reference);
484
+ if (cyclicHandler !== null) {
485
+ // This reference points back to this chunk. We can resolve the cycle by
486
+ // using the value from that handler.
487
+ fulfillReference(reference, cyclicHandler.value);
488
+ resolveListeners.splice(i, 1);
489
+ i--;
490
+ if (rejectListeners !== null) {
491
+ const rejectionIdx = rejectListeners.indexOf(reference);
492
+ if (rejectionIdx !== -1) {
493
+ rejectListeners.splice(rejectionIdx, 1);
494
+ }
495
+ }
496
+ }
497
+ }
498
+ }
499
+ // Fallthrough
500
+ case PENDING:
501
if (chunk.value) {
502
for (let i = 0; i < resolveListeners.length; i++) {
503
chunk.value.push(resolveListeners[i]);
519
break;
520
case ERRORED:
521
if (rejectListeners) {
450
- wakeChunk(rejectListeners, chunk.reason);
522
+ rejectChunk(rejectListeners, chunk.reason);
523
}
524
break;
525
}
540
erroredChunk.status = ERRORED;
541
erroredChunk.reason = error;
542
if (listeners !== null) {
471
- wakeChunk(listeners, error);
543
+ rejectChunk(listeners, error);
544
}
545
}
546
670
}
671
}
672
673
+type InitializationReference = {
674
+ response: Response, // TODO: Remove Response from here and pass it through instead.
675
+ handler: InitializationHandler,
676
+ parentObject: Object,
677
+ key: string,
678
+ map: (
679
+ response: Response,
680
+ model: any,
681
+ parentObject: Object,
682
+ key: string,
683
+ ) => any,
684
+ path: Array<string>,
685
+};
686
type InitializationHandler = {
687
parent: null | InitializationHandler,
688
chunk: null | BlockedChunk<any>,
1083
return chunk;
1084
}
1085
1086
+function fulfillReference(
1087
+ reference: InitializationReference,
1088
+ value: any,
1089
+): void {
1090
+ const {response, handler, parentObject, key, map, path} = reference;
1091
+
1092
+ for (let i = 1; i < path.length; i++) {
1093
+ while (value.$$typeof === REACT_LAZY_TYPE) {
1094
+ // We never expect to see a Lazy node on this path because we encode those as
1095
+ // separate models. This must mean that we have inserted an extra lazy node
1096
+ // e.g. to replace a blocked element. We must instead look for it inside.
1097
+ const referencedChunk: SomeChunk<any> = value._payload;
1098
+ if (referencedChunk === handler.chunk) {
1099
+ // This is a reference to the thing we're currently blocking. We can peak
1100
+ // inside of it to get the value.
1101
+ value = handler.value;
1102
+ continue;
1103
+ } else {
1104
+ switch (referencedChunk.status) {
1105
+ case RESOLVED_MODEL:
1106
+ initializeModelChunk(referencedChunk);
1107
+ break;
1108
+ case RESOLVED_MODULE:
1109
+ initializeModuleChunk(referencedChunk);
1110
+ break;
1111
+ }
1112
+ switch (referencedChunk.status) {
1113
+ case INITIALIZED: {
1114
+ value = referencedChunk.value;
1115
+ continue;
1116
+ }
1117
+ case BLOCKED: {
1118
+ // It is possible that we're blocked on our own chunk if it's a cycle.
1119
+ // Before adding the listener to the inner chunk, let's check if it would
1120
+ // result in a cycle.
1121
+ const cyclicHandler = resolveBlockedCycle(
1122
+ referencedChunk,
1123
+ reference,
1124
+ );
1125
+ if (cyclicHandler !== null) {
1126
+ // This reference points back to this chunk. We can resolve the cycle by
1127
+ // using the value from that handler.
1128
+ value = cyclicHandler.value;
1129
+ continue;
1130
+ }
1131
+ // Fallthrough
1132
+ }
1133
+ case PENDING: {
1134
+ // If we're not yet initialized we need to skip what we've already drilled
1135
+ // through and then wait for the next value to become available.
1136
+ path.splice(0, i - 1);
1137
+ // Add "listener" to our new chunk dependency.
1138
+ if (referencedChunk.value === null) {
1139
+ referencedChunk.value = [reference];
1140
+ } else {
1141
+ referencedChunk.value.push(reference);
1142
+ }
1143
+ if (referencedChunk.reason === null) {
1144
+ referencedChunk.reason = [reference];
1145
+ } else {
1146
+ referencedChunk.reason.push(reference);
1147
+ }
1148
+ return;
1149
+ }
1150
+ case HALTED: {
1151
+ // Do nothing. We couldn't fulfill.
1152
+ // TODO: Mark downstreams as halted too.
1153
+ return;
1154
+ }
1155
+ default: {
1156
+ rejectReference(reference, referencedChunk.reason);
1157
+ return;
1158
+ }
1159
+ }
1160
+ }
1161
+ }
1162
+ value = value[path[i]];
1163
+ }
1164
+ const mappedValue = map(response, value, parentObject, key);
1165
+ parentObject[key] = mappedValue;
1166
+
1167
+ // If this is the root object for a model reference, where `handler.value`
1168
+ // is a stale `null`, the resolved value can be used directly.
1169
+ if (key === '' && handler.value === null) {
1170
+ handler.value = mappedValue;
1171
+ }
1172
+
1173
+ // If the parent object is an unparsed React element tuple, we also need to
1174
+ // update the props and owner of the parsed element object (i.e.
1175
+ // handler.value).
1176
+ if (
1177
+ parentObject[0] === REACT_ELEMENT_TYPE &&
1178
+ typeof handler.value === 'object' &&
1179
+ handler.value !== null &&
1180
+ handler.value.$$typeof === REACT_ELEMENT_TYPE
1181
+ ) {
1182
+ const element: any = handler.value;
1183
+ switch (key) {
1184
+ case '3':
1185
+ element.props = mappedValue;
1186
+ break;
1187
+ case '4':
1188
+ if (__DEV__) {
1189
+ element._owner = mappedValue;
1190
+ }
1191
+ break;
1192
+ case '5':
1193
+ if (__DEV__) {
1194
+ element._debugStack = mappedValue;
1195
+ }
1196
+ break;
1197
+ }
1198
+ }
1199
+
1200
+ handler.deps--;
1201
+
1202
+ if (handler.deps === 0) {
1203
+ const chunk = handler.chunk;
1204
+ if (chunk === null || chunk.status !== BLOCKED) {
1205
+ return;
1206
+ }
1207
+ const resolveListeners = chunk.value;
1208
+ const initializedChunk: InitializedChunk<any> = (chunk: any);
1209
+ initializedChunk.status = INITIALIZED;
1210
+ initializedChunk.value = handler.value;
1211
+ if (resolveListeners !== null) {
1212
+ wakeChunk(resolveListeners, handler.value);
1213
+ }
1214
+ }
1215
+}
1216
+
1217
+function rejectReference(
1218
+ reference: InitializationReference,
1219
+ error: mixed,
1220
+): void {
1221
+ const {handler} = reference;
1222
+
1223
+ if (handler.errored) {
1224
+ // We've already errored. We could instead build up an AggregateError
1225
+ // but if there are multiple errors we just take the first one like
1226
+ // Promise.all.
1227
+ return;
1228
+ }
1229
+ const blockedValue = handler.value;
1230
+ handler.errored = true;
1231
+ handler.value = error;
1232
+ const chunk = handler.chunk;
1233
+ if (chunk === null || chunk.status !== BLOCKED) {
1234
+ return;
1235
+ }
1236
+
1237
+ if (__DEV__) {
1238
+ if (
1239
+ typeof blockedValue === 'object' &&
1240
+ blockedValue !== null &&
1241
+ blockedValue.$$typeof === REACT_ELEMENT_TYPE
1242
+ ) {
1243
+ const element = blockedValue;
1244
+ // Conceptually the error happened inside this Element but right before
1245
+ // it was rendered. We don't have a client side component to render but
1246
+ // we can add some DebugInfo to explain that this was conceptually a
1247
+ // Server side error that errored inside this element. That way any stack
1248
+ // traces will point to the nearest JSX that errored - e.g. during
1249
+ // serialization.
1250
+ const erroredComponent: ReactComponentInfo = {
1251
+ name: getComponentNameFromType(element.type) || '',
1252
+ owner: element._owner,
1253
+ };
1254
+ // $FlowFixMe[cannot-write]
1255
+ erroredComponent.debugStack = element._debugStack;
1256
+ if (supportsCreateTask) {
1257
+ // $FlowFixMe[cannot-write]
1258
+ erroredComponent.debugTask = element._debugTask;
1259
+ }
1260
+ const chunkDebugInfo: ReactDebugInfo =
1261
+ chunk._debugInfo || (chunk._debugInfo = []);
1262
+ chunkDebugInfo.push(erroredComponent);
1263
+ }
1264
+ }
1265
+
1266
+ triggerErrorOnChunk(chunk, error);
1267
+}
1268
+
1269
function waitForReference<T>(
1002
- referencedChunk: SomeChunk<T>,
1270
+ referencedChunk: PendingChunk<T> | BlockedChunk<T>,
1271
parentObject: Object,
1272
key: string,
1273
response: Response,
1288
};
1289
}
1290
1023
- function fulfill(value: any): void {
1024
- for (let i = 1; i < path.length; i++) {
1025
- while (value.$$typeof === REACT_LAZY_TYPE) {
1026
- // We never expect to see a Lazy node on this path because we encode those as
1027
- // separate models. This must mean that we have inserted an extra lazy node
1028
- // e.g. to replace a blocked element. We must instead look for it inside.
1029
- const chunk: SomeChunk<any> = value._payload;
1030
- if (chunk === handler.chunk) {
1031
- // This is a reference to the thing we're currently blocking. We can peak
1032
- // inside of it to get the value.
1033
- value = handler.value;
1034
- continue;
1035
- } else if (chunk.status === INITIALIZED) {
1036
- value = chunk.value;
1037
- continue;
1038
- } else {
1039
- // If we're not yet initialized we need to skip what we've already drilled
1040
- // through and then wait for the next value to become available.
1041
- path.splice(0, i - 1);
1042
- chunk.then(fulfill, reject);
1043
- return;
1044
- }
1045
- }
1046
- value = value[path[i]];
1047
- }
1048
- const mappedValue = map(response, value, parentObject, key);
1049
- parentObject[key] = mappedValue;
1050
-
1051
- // If this is the root object for a model reference, where `handler.value`
1052
- // is a stale `null`, the resolved value can be used directly.
1053
- if (key === '' && handler.value === null) {
1054
- handler.value = mappedValue;
1055
- }
1056
-
1057
- // If the parent object is an unparsed React element tuple, we also need to
1058
- // update the props and owner of the parsed element object (i.e.
1059
- // handler.value).
1060
- if (
1061
- parentObject[0] === REACT_ELEMENT_TYPE &&
1062
- typeof handler.value === 'object' &&
1063
- handler.value !== null &&
1064
- handler.value.$$typeof === REACT_ELEMENT_TYPE
1065
- ) {
1066
- const element: any = handler.value;
1067
- switch (key) {
1068
- case '3':
1069
- element.props = mappedValue;
1070
- break;
1071
- case '4':
1072
- if (__DEV__) {
1073
- element._owner = mappedValue;
1074
- }
1075
- break;
1076
- case '5':
1077
- if (__DEV__) {
1078
- element._debugStack = mappedValue;
1079
- }
1080
- break;
1081
- }
1082
- }
1083
-
1084
- handler.deps--;
1291
+ const reference: InitializationReference = {
1292
+ response,
1293
+ handler,
1294
+ parentObject,
1295
+ key,
1296
+ map,
1297
+ path,
1298
+ };
1299
1086
- if (handler.deps === 0) {
1087
- const chunk = handler.chunk;
1088
- if (chunk === null || chunk.status !== BLOCKED) {
1089
- return;
1090
- }
1091
- const resolveListeners = chunk.value;
1092
- const initializedChunk: InitializedChunk<T> = (chunk: any);
1093
- initializedChunk.status = INITIALIZED;
1094
- initializedChunk.value = handler.value;
1095
- if (resolveListeners !== null) {
1096
- wakeChunk(resolveListeners, handler.value);
1097
- }
1098
- }
1099
- }
1100
- // Use to avoid the microtask resolution in DEV.
1101
- if (__DEV__ && enableAsyncDebugInfo) {
1102
- (fulfill: any).isReactInternalListener = true;
1300
+ // Add "listener".
1301
+ if (referencedChunk.value === null) {
1302
+ referencedChunk.value = [reference];
1303
+ } else {
1304
+ referencedChunk.value.push(reference);
1305
}
1104
-
1105
- function reject(error: mixed): void {
1106
- if (handler.errored) {
1107
- // We've already errored. We could instead build up an AggregateError
1108
- // but if there are multiple errors we just take the first one like
1109
- // Promise.all.
1110
- return;
1111
- }
1112
- const blockedValue = handler.value;
1113
- handler.errored = true;
1114
- handler.value = error;
1115
- const chunk = handler.chunk;
1116
- if (chunk === null || chunk.status !== BLOCKED) {
1117
- return;
1118
- }
1119
-
1120
- if (__DEV__) {
1121
- if (
1122
- typeof blockedValue === 'object' &&
1123
- blockedValue !== null &&
1124
- blockedValue.$$typeof === REACT_ELEMENT_TYPE
1125
- ) {
1126
- const element = blockedValue;
1127
- // Conceptually the error happened inside this Element but right before
1128
- // it was rendered. We don't have a client side component to render but
1129
- // we can add some DebugInfo to explain that this was conceptually a
1130
- // Server side error that errored inside this element. That way any stack
1131
- // traces will point to the nearest JSX that errored - e.g. during
1132
- // serialization.
1133
- const erroredComponent: ReactComponentInfo = {
1134
- name: getComponentNameFromType(element.type) || '',
1135
- owner: element._owner,
1136
- };
1137
- // $FlowFixMe[cannot-write]
1138
- erroredComponent.debugStack = element._debugStack;
1139
- if (supportsCreateTask) {
1140
- // $FlowFixMe[cannot-write]
1141
- erroredComponent.debugTask = element._debugTask;
1142
- }
1143
- const chunkDebugInfo: ReactDebugInfo =
1144
- chunk._debugInfo || (chunk._debugInfo = []);
1145
- chunkDebugInfo.push(erroredComponent);
1146
- }
1147
- }
1148
-
1149
- triggerErrorOnChunk(chunk, error);
1306
+ if (referencedChunk.reason === null) {
1307
+ referencedChunk.reason = [reference];
1308
+ } else {
1309
+ referencedChunk.reason.push(reference);
1310
}
1311
1152
- referencedChunk.then(fulfill, reject);
1153
-
1312
// Return a place holder value for now.
1313
return (null: any);
1314
}
1521
for (let i = 1; i < path.length; i++) {
1522
while (value.$$typeof === REACT_LAZY_TYPE) {
1523
const referencedChunk: SomeChunk<any> = value._payload;
1366
- if (referencedChunk.status === INITIALIZED) {
1367
- value = referencedChunk.value;
1368
- } else {
1369
- return waitForReference(
1370
- referencedChunk,
1371
- parentObject,
1372
- key,
1373
- response,
1374
- map,
1375
- path.slice(i - 1),
1376
- );
1524
+ switch (referencedChunk.status) {
1525
+ case RESOLVED_MODEL:
1526
+ initializeModelChunk(referencedChunk);
1527
+ break;
1528
+ case RESOLVED_MODULE:
1529
+ initializeModuleChunk(referencedChunk);
1530
+ break;
1531
+ }
1532
+ switch (referencedChunk.status) {
1533
+ case INITIALIZED: {
1534
+ value = referencedChunk.value;
1535
+ break;
1536
+ }
1537
+ case BLOCKED:
1538
+ case PENDING: {
1539
+ return waitForReference(
1540
+ referencedChunk,
1541
+ parentObject,
1542
+ key,
1543
+ response,
1544
+ map,
1545
+ path.slice(i - 1),
1546
+ );
1547
+ }
1548
+ case HALTED: {
1549
+ // Add a dependency that will never resolve.
1550
+ // TODO: Mark downstreams as halted too.
1551
+ let handler: InitializationHandler;
1552
+ if (initializingHandler) {
1553
+ handler = initializingHandler;
1554
+ handler.deps++;
1555
+ } else {
1556
+ handler = initializingHandler = {
1557
+ parent: null,
1558
+ chunk: null,
1559
+ value: null,
1560
+ deps: 1,
1561
+ errored: false,
1562
+ };
1563
+ }
1564
+ return (null: any);
1565
+ }
1566
+ default: {
1567
+ // This is an error. Instead of erroring directly, we're going to encode this on
1568
+ // an initialization handler so that we can catch it at the nearest Element.
1569
+ if (initializingHandler) {
1570
+ initializingHandler.errored = true;
1571
+ initializingHandler.value = referencedChunk.reason;
1572
+ } else {
1573
+ initializingHandler = {
1574
+ parent: null,
1575
+ chunk: null,
1576
+ value: referencedChunk.reason,
1577
+ deps: 0,
1578
+ errored: true,
1579
+ };
1580
+ }
1581
+ return (null: any);
1582
+ }
1583
}
1584
}
1585
value = value[path[i]];
1614
return chunkValue;
1615
case PENDING:
1616
case BLOCKED:
1411
- case HALTED:
1617
return waitForReference(chunk, parentObject, key, response, map, path);
1618
+ case HALTED: {
1619
+ // Add a dependency that will never resolve.
1620
+ // TODO: Mark downstreams as halted too.
1621
+ let handler: InitializationHandler;
1622
+ if (initializingHandler) {
1623
+ handler = initializingHandler;
1624
+ handler.deps++;
1625
+ } else {
1626
+ handler = initializingHandler = {
1627
+ parent: null,
1628
+ chunk: null,
1629
+ value: null,
1630
+ deps: 1,
1631
+ errored: false,
1632
+ };
1633
+ }
1634
+ return (null: any);
1635
+ }
1636
default:
1637
// This is an error. Instead of erroring directly, we're going to encode this on
1638
// an initialization handler so that we can catch it at the nearest Element.