353
interface Reference {}
354
355
export type Request = {
356
- status: 0 | 1 | 2 | 3,
356
+ status: 10 | 11 | 12 | 13,
357
+ type: 20 | 21,
358
flushScheduled: boolean,
359
fatalError: mixed,
360
destination: null | Destination,
426
// Noop
427
}
428
428
-const OPEN = 0;
429
-const ABORTING = 1;
430
-const CLOSING = 2;
431
-const CLOSED = 3;
429
+const OPEN = 10;
430
+const ABORTING = 11;
431
+const CLOSING = 12;
432
+const CLOSED = 13;
433
+
434
+const RENDER = 20;
435
+const PRERENDER = 21;
436
437
function RequestInstance(
438
this: $FlowFixMe,
439
+ type: 20 | 21,
440
model: ReactClientValue,
441
bundlerConfig: ClientManifest,
442
onError: void | ((error: mixed) => ?string),
445
temporaryReferences: void | TemporaryReferenceSet,
446
environmentName: void | string | (() => string), // DEV-only
447
filterStackFrame: void | ((url: string, functionName: string) => boolean), // DEV-only
443
- onAllReady: void | (() => void),
444
- onFatalError: void | ((error: mixed) => void),
448
+ onAllReady: () => void,
449
+ onFatalError: (error: mixed) => void,
450
) {
451
if (
452
ReactSharedInternals.A !== null &&
471
TaintRegistryPendingRequests.add(cleanupQueue);
472
}
473
const hints = createHints();
474
+ this.type = type;
475
this.status = OPEN;
476
this.flushScheduled = false;
477
this.fatalError = null;
499
this.onError = onError === undefined ? defaultErrorHandler : onError;
500
this.onPostpone =
501
onPostpone === undefined ? defaultPostponeHandler : onPostpone;
496
- this.onAllReady = onAllReady === undefined ? noop : onAllReady;
497
- this.onFatalError = onFatalError === undefined ? noop : onFatalError;
502
+ this.onAllReady = onAllReady;
503
+ this.onFatalError = onFatalError;
504
505
if (__DEV__) {
506
this.environmentName =
528
pingedTasks.push(rootTask);
529
}
530
525
-function noop(): void {}
531
+function noop() {}
532
533
export function createRequest(
534
model: ReactClientValue,
539
temporaryReferences: void | TemporaryReferenceSet,
540
environmentName: void | string | (() => string), // DEV-only
541
filterStackFrame: void | ((url: string, functionName: string) => boolean), // DEV-only
536
- onAllReady: void | (() => void),
537
- onFatalError: void | (() => void),
542
): Request {
543
// $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors
544
return new RequestInstance(
545
+ RENDER,
546
+ model,
547
+ bundlerConfig,
548
+ onError,
549
+ identifierPrefix,
550
+ onPostpone,
551
+ temporaryReferences,
552
+ environmentName,
553
+ filterStackFrame,
554
+ noop,
555
+ noop,
556
+ );
557
+}
558
+
559
+export function createPrerenderRequest(
560
+ model: ReactClientValue,
561
+ bundlerConfig: ClientManifest,
562
+ onAllReady: () => void,
563
+ onFatalError: () => void,
564
+ onError: void | ((error: mixed) => ?string),
565
+ identifierPrefix?: string,
566
+ onPostpone: void | ((reason: string) => void),
567
+ temporaryReferences: void | TemporaryReferenceSet,
568
+ environmentName: void | string | (() => string), // DEV-only
569
+ filterStackFrame: void | ((url: string, functionName: string) => boolean), // DEV-only
570
+): Request {
571
+ // $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors
572
+ return new RequestInstance(
573
+ PRERENDER,
574
model,
575
bundlerConfig,
576
onError,
649
// We can no longer accept any resolved values
650
request.abortableTasks.delete(newTask);
651
newTask.status = ABORTED;
619
- if (enableHalt && request.fatalError === haltSymbol) {
620
- emitBlockedChunk(request, newTask.id);
621
- } else {
622
- const errorId: number = (request.fatalError: any);
623
- const model = stringify(serializeByValueID(errorId));
624
- emitModelChunk(request, newTask.id, model);
625
- }
652
+ const errorId: number = (request.fatalError: any);
653
+ const model = stringify(serializeByValueID(errorId));
654
+ emitModelChunk(request, newTask.id, model);
655
return newTask.id;
656
}
657
if (typeof thenable.status === 'string') {
761
}
762
763
if (entry.done) {
735
- request.abortListeners.delete(error);
764
+ request.abortListeners.delete(abortStream);
765
const endStreamRow = streamTask.id.toString(16) + ':C\n';
766
request.completedRegularChunks.push(stringToChunk(endStreamRow));
767
enqueueFlush(request);
783
return;
784
}
785
aborted = true;
757
- request.abortListeners.delete(error);
786
+ request.abortListeners.delete(abortStream);
787
+ const digest = logRecoverableError(request, reason, streamTask);
788
+ emitErrorChunk(request, streamTask.id, digest, reason);
789
+ enqueueFlush(request);
790
759
- let cancelWith: mixed;
760
- if (enableHalt && request.fatalError === haltSymbol) {
761
- cancelWith = reason;
762
- } else if (
791
+ // $FlowFixMe should be able to pass mixed
792
+ reader.cancel(reason).then(error, error);
793
+ }
794
+ function abortStream(reason: mixed) {
795
+ if (aborted) {
796
+ return;
797
+ }
798
+ aborted = true;
799
+ request.abortListeners.delete(abortStream);
800
+ if (
801
enablePostpone &&
802
typeof reason === 'object' &&
803
reason !== null &&
804
(reason: any).$$typeof === REACT_POSTPONE_TYPE
805
) {
768
- cancelWith = reason;
806
const postponeInstance: Postpone = (reason: any);
807
logPostpone(request, postponeInstance.message, streamTask);
771
- emitPostponeChunk(request, streamTask.id, postponeInstance);
772
- enqueueFlush(request);
808
+ if (enableHalt && request.type === PRERENDER) {
809
+ request.pendingChunks--;
810
+ } else {
811
+ emitPostponeChunk(request, streamTask.id, postponeInstance);
812
+ enqueueFlush(request);
813
+ }
814
} else {
774
- cancelWith = reason;
815
const digest = logRecoverableError(request, reason, streamTask);
776
- emitErrorChunk(request, streamTask.id, digest, reason);
777
- enqueueFlush(request);
816
+ if (enableHalt && request.type === PRERENDER) {
817
+ request.pendingChunks--;
818
+ } else {
819
+ emitErrorChunk(request, streamTask.id, digest, reason);
820
+ enqueueFlush(request);
821
+ }
822
}
823
824
// $FlowFixMe should be able to pass mixed
781
- reader.cancel(cancelWith).then(error, error);
825
+ reader.cancel(reason).then(error, error);
826
}
827
784
- request.abortListeners.add(error);
828
+ request.abortListeners.add(abortStream);
829
reader.read().then(progress, error);
830
return serializeByValueID(streamTask.id);
831
}
881
}
882
883
if (entry.done) {
840
- request.abortListeners.delete(error);
884
+ request.abortListeners.delete(abortIterable);
885
let endStreamRow;
886
if (entry.value === undefined) {
887
endStreamRow = streamTask.id.toString(16) + ':C\n';
925
return;
926
}
927
aborted = true;
884
- request.abortListeners.delete(error);
885
- let throwWith: mixed;
886
- if (enableHalt && request.fatalError === haltSymbol) {
887
- throwWith = reason;
888
- } else if (
928
+ request.abortListeners.delete(abortIterable);
929
+ const digest = logRecoverableError(request, reason, streamTask);
930
+ emitErrorChunk(request, streamTask.id, digest, reason);
931
+ enqueueFlush(request);
932
+ if (typeof (iterator: any).throw === 'function') {
933
+ // The iterator protocol doesn't necessarily include this but a generator do.
934
+ // $FlowFixMe should be able to pass mixed
935
+ iterator.throw(reason).then(error, error);
936
+ }
937
+ }
938
+ function abortIterable(reason: mixed) {
939
+ if (aborted) {
940
+ return;
941
+ }
942
+ aborted = true;
943
+ request.abortListeners.delete(abortIterable);
944
+ if (
945
enablePostpone &&
946
typeof reason === 'object' &&
947
reason !== null &&
948
(reason: any).$$typeof === REACT_POSTPONE_TYPE
949
) {
894
- throwWith = reason;
950
const postponeInstance: Postpone = (reason: any);
951
logPostpone(request, postponeInstance.message, streamTask);
897
- emitPostponeChunk(request, streamTask.id, postponeInstance);
898
- enqueueFlush(request);
952
+ if (enableHalt && request.type === PRERENDER) {
953
+ request.pendingChunks--;
954
+ } else {
955
+ emitPostponeChunk(request, streamTask.id, postponeInstance);
956
+ enqueueFlush(request);
957
+ }
958
} else {
900
- throwWith = reason;
959
const digest = logRecoverableError(request, reason, streamTask);
902
- emitErrorChunk(request, streamTask.id, digest, reason);
903
- enqueueFlush(request);
960
+ if (enableHalt && request.type === PRERENDER) {
961
+ request.pendingChunks--;
962
+ } else {
963
+ emitErrorChunk(request, streamTask.id, digest, reason);
964
+ enqueueFlush(request);
965
+ }
966
}
967
if (typeof (iterator: any).throw === 'function') {
968
// The iterator protocol doesn't necessarily include this but a generator do.
969
// $FlowFixMe should be able to pass mixed
908
- iterator.throw(throwWith).then(error, error);
970
+ iterator.throw(reason).then(error, error);
971
}
972
}
911
- request.abortListeners.add(error);
973
+ request.abortListeners.add(abortIterable);
974
if (__DEV__) {
975
callIteratorInDEV(iterator, progress, error);
976
} else {
2163
return;
2164
}
2165
if (entry.done) {
2104
- request.abortListeners.delete(error);
2166
+ request.abortListeners.delete(abortBlob);
2167
aborted = true;
2168
pingTask(request, newTask);
2169
return;
2173
// $FlowFixMe[incompatible-call]
2174
return reader.read().then(progress).catch(error);
2175
}
2114
-
2176
function error(reason: mixed) {
2177
if (aborted) {
2178
return;
2179
}
2180
aborted = true;
2120
- request.abortListeners.delete(error);
2121
- let cancelWith: mixed;
2122
- if (enableHalt && request.fatalError === haltSymbol) {
2123
- cancelWith = reason;
2181
+ request.abortListeners.delete(abortBlob);
2182
+ const digest = logRecoverableError(request, reason, newTask);
2183
+ emitErrorChunk(request, newTask.id, digest, reason);
2184
+ enqueueFlush(request);
2185
+ // $FlowFixMe should be able to pass mixed
2186
+ reader.cancel(reason).then(error, error);
2187
+ }
2188
+ function abortBlob(reason: mixed) {
2189
+ if (aborted) {
2190
+ return;
2191
+ }
2192
+ aborted = true;
2193
+ request.abortListeners.delete(abortBlob);
2194
+ if (
2195
+ enablePostpone &&
2196
+ typeof reason === 'object' &&
2197
+ reason !== null &&
2198
+ (reason: any).$$typeof === REACT_POSTPONE_TYPE
2199
+ ) {
2200
+ const postponeInstance: Postpone = (reason: any);
2201
+ logPostpone(request, postponeInstance.message, newTask);
2202
+ if (enableHalt && request.type === PRERENDER) {
2203
+ request.pendingChunks--;
2204
+ } else {
2205
+ emitPostponeChunk(request, newTask.id, postponeInstance);
2206
+ enqueueFlush(request);
2207
+ }
2208
} else {
2125
- cancelWith = reason;
2209
const digest = logRecoverableError(request, reason, newTask);
2127
- emitErrorChunk(request, newTask.id, digest, reason);
2128
- request.abortableTasks.delete(newTask);
2129
- enqueueFlush(request);
2210
+ if (enableHalt && request.type === PRERENDER) {
2211
+ request.pendingChunks--;
2212
+ } else {
2213
+ emitErrorChunk(request, newTask.id, digest, reason);
2214
+ enqueueFlush(request);
2215
+ }
2216
}
2217
// $FlowFixMe should be able to pass mixed
2132
- reader.cancel(cancelWith).then(error, error);
2218
+ reader.cancel(reason).then(error, error);
2219
}
2220
2135
- request.abortListeners.add(error);
2221
+ request.abortListeners.add(abortBlob);
2222
2223
// $FlowFixMe[incompatible-call]
2224
reader.read().then(progress).catch(error);
3087
request.completedErrorChunks.push(processedChunk);
3088
}
3089
3004
-function emitBlockedChunk(request: Request, id: number): void {
3005
- const row = serializeRowHeader('#', id) + '\n';
3006
- const processedChunk = stringToChunk(row);
3007
- request.completedErrorChunks.push(processedChunk);
3008
-}
3009
-
3090
function emitErrorChunk(
3091
request: Request,
3092
id: number,
3835
if (request.status === ABORTING) {
3836
request.abortableTasks.delete(task);
3837
task.status = ABORTED;
3758
- if (enableHalt && request.fatalError === haltSymbol) {
3759
- emitBlockedChunk(request, task.id);
3760
- } else {
3761
- const errorId: number = (request.fatalError: any);
3762
- const model = stringify(serializeByValueID(errorId));
3763
- emitModelChunk(request, task.id, model);
3764
- }
3838
+ const errorId: number = (request.fatalError: any);
3839
+ const model = stringify(serializeByValueID(errorId));
3840
+ emitModelChunk(request, task.id, model);
3841
return;
3842
}
3843
// Something suspended again, let's pick it back up later.
3859
if (request.status === ABORTING) {
3860
request.abortableTasks.delete(task);
3861
task.status = ABORTED;
3786
- if (enableHalt && request.fatalError === haltSymbol) {
3787
- emitBlockedChunk(request, task.id);
3788
- } else {
3789
- const errorId: number = (request.fatalError: any);
3790
- const model = stringify(serializeByValueID(errorId));
3791
- emitModelChunk(request, task.id, model);
3792
- }
3862
+ const errorId: number = (request.fatalError: any);
3863
+ const model = stringify(serializeByValueID(errorId));
3864
+ emitModelChunk(request, task.id, model);
3865
return;
3866
}
3867
3916
// We can ping after completing but if this happens there already
3917
// wouldn't be any abortable tasks. So we only call allReady after
3918
// the work which actually completed the last pending task
3847
- allReady(request);
3919
+ const onAllReady = request.onAllReady;
3920
+ onAllReady();
3921
}
3922
} catch (error) {
3923
logRecoverableError(request, error, null);
4080
request.destination = null;
4081
}
4082
4010
-// This is called to early terminate a request. It creates an error at all pending tasks.
4083
export function abort(request: Request, reason: mixed): void {
4084
try {
4085
if (request.status === OPEN) {
4086
request.status = ABORTING;
4087
}
4088
const abortableTasks = request.abortableTasks;
4017
- // We have tasks to abort. We'll emit one error row and then emit a reference
4018
- // to that row from every row that's still remaining.
4089
if (abortableTasks.size > 0) {
4020
- request.pendingChunks++;
4090
+ // We have tasks to abort. We'll emit one error row and then emit a reference
4091
+ // to that row from every row that's still remaining if we are rendering. If we
4092
+ // are prerendering (and halt semantics are enabled) we will refer to an error row
4093
+ // but not actually emit it so the reciever can at that point rather than error.
4094
const errorId = request.nextChunkId++;
4095
request.fatalError = errorId;
4096
if (
4101
) {
4102
const postponeInstance: Postpone = (reason: any);
4103
logPostpone(request, postponeInstance.message, null);
4031
- emitPostponeChunk(request, errorId, postponeInstance);
4104
+ if (!enableHalt || request.type === PRERENDER) {
4105
+ // When prerendering with halt semantics we omit the referred to postpone.
4106
+ request.pendingChunks++;
4107
+ emitPostponeChunk(request, errorId, postponeInstance);
4108
+ }
4109
} else {
4110
const error =
4111
reason === undefined
4120
)
4121
: reason;
4122
const digest = logRecoverableError(request, error, null);
4046
- emitErrorChunk(request, errorId, digest, error);
4123
+ if (!enableHalt || request.type === RENDER) {
4124
+ // When prerendering with halt semantics we omit the referred to error.
4125
+ request.pendingChunks++;
4126
+ emitErrorChunk(request, errorId, digest, error);
4127
+ }
4128
}
4129
abortableTasks.forEach(task => abortTask(task, request, errorId));
4130
abortableTasks.clear();
4050
- allReady(request);
4131
+ const onAllReady = request.onAllReady;
4132
+ onAllReady();
4133
}
4134
const abortListeners = request.abortListeners;
4135
if (abortListeners.size > 0) {
4169
fatalError(request, error);
4170
}
4171
}
4090
-
4091
-const haltSymbol = Symbol('halt');
4092
-
4093
-// This is called to stop rendering without erroring. All unfinished work is represented Promises
4094
-// that never resolve.
4095
-export function halt(request: Request, reason: mixed): void {
4096
- try {
4097
- if (request.status === OPEN) {
4098
- request.status = ABORTING;
4099
- }
4100
- request.fatalError = haltSymbol;
4101
- const abortableTasks = request.abortableTasks;
4102
- // We have tasks to abort. We'll emit one error row and then emit a reference
4103
- // to that row from every row that's still remaining.
4104
- if (abortableTasks.size > 0) {
4105
- request.pendingChunks++;
4106
- const errorId = request.nextChunkId++;
4107
- emitBlockedChunk(request, errorId);
4108
- abortableTasks.forEach(task => abortTask(task, request, errorId));
4109
- abortableTasks.clear();
4110
- allReady(request);
4111
- }
4112
- const abortListeners = request.abortListeners;
4113
- if (abortListeners.size > 0) {
4114
- abortListeners.forEach(callback => callback(reason));
4115
- abortListeners.clear();
4116
- }
4117
- if (request.destination !== null) {
4118
- flushCompletedChunks(request, request.destination);
4119
- }
4120
- } catch (error) {
4121
- logRecoverableError(request, error, null);
4122
- fatalError(request, error);
4123
- }
4124
-}
4125
-
4126
-function allReady(request: Request) {
4127
- const onAllReady = request.onAllReady;
4128
- onAllReady();
4129
-}