@@ -649,9 +649,13 @@ function serializeThenable(
649
649
// We can no longer accept any resolved values
650
650
request . abortableTasks . delete ( newTask ) ;
651
651
newTask . status = ABORTED ;
652
- const errorId : number = ( request . fatalError : any ) ;
653
- const model = stringify ( serializeByValueID ( errorId ) ) ;
654
- emitModelChunk ( request , newTask . id , model ) ;
652
+ if ( enableHalt && request . type === PRERENDER ) {
653
+ request . pendingChunks -- ;
654
+ } else {
655
+ const errorId : number = ( request . fatalError : any ) ;
656
+ const model = stringify ( serializeByValueID ( errorId ) ) ;
657
+ emitModelChunk ( request , newTask . id , model ) ;
658
+ }
655
659
return newTask . id ;
656
660
}
657
661
if ( typeof thenable . status === 'string' ) {
@@ -1633,6 +1637,24 @@ function outlineTask(request: Request, task: Task): ReactJSONValue {
1633
1637
return serializeLazyID(newTask.id);
1634
1638
}
1635
1639
1640
+ function outlineHaltedTask (
1641
+ request : Request ,
1642
+ task : Task ,
1643
+ allowLazy : boolean ,
1644
+ ) : ReactJSONValue {
1645
+ // In the future if we track task state for resuming we'll maybe need to
1646
+ // construnct an actual task here but since we're never going to retry it
1647
+ // we just claim the id and serialize it according to the proper convention
1648
+ const taskId = request . nextChunkId ++ ;
1649
+ if ( allowLazy ) {
1650
+ // We're halting in a position that can handle a lazy reference
1651
+ return serializeLazyID ( taskId ) ;
1652
+ } else {
1653
+ // We're halting in a position that needs a value reference
1654
+ return serializeByValueID ( taskId ) ;
1655
+ }
1656
+ }
1657
+
1636
1658
function renderElement (
1637
1659
request : Request ,
1638
1660
task : Task ,
@@ -2278,6 +2300,20 @@ function renderModel(
2278
2300
( ( model : any ) . $$typeof === REACT_ELEMENT_TYPE ||
2279
2301
( model : any ) . $$typeof === REACT_LAZY_TYPE ) ;
2280
2302
2303
+ if ( request . status === ABORTING ) {
2304
+ task . status = ABORTED ;
2305
+ if ( enableHalt && request . type === PRERENDER ) {
2306
+ // This will create a new task and refer to it in this slot
2307
+ // the new task won't be retried because we are aborting
2308
+ return outlineHaltedTask ( request , task , wasReactNode ) ;
2309
+ }
2310
+ const errorId = ( request . fatalError : any ) ;
2311
+ if ( wasReactNode ) {
2312
+ return serializeLazyID ( errorId ) ;
2313
+ }
2314
+ return serializeByValueID ( errorId ) ;
2315
+ }
2316
+
2281
2317
const x =
2282
2318
thrownValue === SuspenseException
2283
2319
? // This is a special type of exception used for Suspense. For historical
@@ -2291,14 +2327,6 @@ function renderModel(
2291
2327
if ( typeof x === 'object' && x !== null ) {
2292
2328
// $FlowFixMe[method-unbinding]
2293
2329
if ( typeof x . then === 'function' ) {
2294
- if ( request . status === ABORTING ) {
2295
- task . status = ABORTED ;
2296
- const errorId : number = ( request . fatalError : any ) ;
2297
- if ( wasReactNode ) {
2298
- return serializeLazyID ( errorId ) ;
2299
- }
2300
- return serializeByValueID ( errorId ) ;
2301
- }
2302
2330
// Something suspended, we'll need to create a new task and resolve it later.
2303
2331
const newTask = createTask (
2304
2332
request ,
@@ -2344,15 +2372,6 @@ function renderModel(
2344
2372
}
2345
2373
}
2346
2374
2347
- if ( request . status === ABORTING ) {
2348
- task . status = ABORTED ;
2349
- const errorId : number = ( request . fatalError : any ) ;
2350
- if ( wasReactNode ) {
2351
- return serializeLazyID ( errorId ) ;
2352
- }
2353
- return serializeByValueID ( errorId ) ;
2354
- }
2355
-
2356
2375
// Restore the context. We assume that this will be restored by the inner
2357
2376
// functions in case nothing throws so we don't use "finally" here.
2358
2377
task . keyPath = prevKeyPath ;
@@ -3820,6 +3839,22 @@ function retryTask(request: Request, task: Task): void {
3820
3839
request.abortableTasks.delete(task);
3821
3840
task.status = COMPLETED;
3822
3841
} catch ( thrownValue ) {
3842
+ if ( request . status === ABORTING ) {
3843
+ request . abortableTasks . delete ( task ) ;
3844
+ task . status = ABORTED ;
3845
+ if ( enableHalt && request . type === PRERENDER ) {
3846
+ // When aborting a prerener with halt semantics we don't emit
3847
+ // anything into the slot for a task that aborts, it remains unresolved
3848
+ request. pendingChunks -- ;
3849
+ } else {
3850
+ // Otherwise we emit an error chunk into the task slot.
3851
+ const errorId : number = ( request . fatalError : any ) ;
3852
+ const model = stringify ( serializeByValueID ( errorId ) ) ;
3853
+ emitModelChunk ( request , task . id , model ) ;
3854
+ }
3855
+ return;
3856
+ }
3857
+
3823
3858
const x =
3824
3859
thrownValue === SuspenseException
3825
3860
? // This is a special type of exception used for Suspense. For historical
@@ -3832,14 +3867,6 @@ function retryTask(request: Request, task: Task): void {
3832
3867
if ( typeof x === 'object ' && x !== null ) {
3833
3868
// $FlowFixMe[method-unbinding]
3834
3869
if ( typeof x . then === 'function' ) {
3835
- if ( request . status === ABORTING ) {
3836
- request . abortableTasks . delete ( task ) ;
3837
- task . status = ABORTED ;
3838
- const errorId : number = ( request . fatalError : any ) ;
3839
- const model = stringify ( serializeByValueID ( errorId ) ) ;
3840
- emitModelChunk ( request , task . id , model ) ;
3841
- return ;
3842
- }
3843
3870
// Something suspended again, let's pick it back up later.
3844
3871
task . status = PENDING ;
3845
3872
task . thenableState = getThenableStateAfterSuspending ( ) ;
@@ -3856,15 +3883,6 @@ function retryTask(request: Request, task: Task): void {
3856
3883
}
3857
3884
}
3858
3885
3859
- if ( request . status === ABORTING ) {
3860
- request . abortableTasks . delete ( task ) ;
3861
- task . status = ABORTED ;
3862
- const errorId : number = ( request . fatalError : any ) ;
3863
- const model = stringify ( serializeByValueID ( errorId ) ) ;
3864
- emitModelChunk ( request , task . id , model ) ;
3865
- return ;
3866
- }
3867
-
3868
3886
request . abortableTasks . delete ( task ) ;
3869
3887
task . status = ERRORED ;
3870
3888
const digest = logRecoverableError ( request , x , task ) ;
@@ -3942,6 +3960,17 @@ function abortTask(task: Task, request: Request, errorId: number): void {
3942
3960
request.completedErrorChunks.push(processedChunk);
3943
3961
}
3944
3962
3963
+ function haltTask ( task : Task , request : Request ) : void {
3964
+ if ( task . status === RENDERING ) {
3965
+ // this task will be halted by the render
3966
+ return ;
3967
+ }
3968
+ task.status = ABORTED;
3969
+ // We don't actually emit anything for this task id because we are intentionally
3970
+ // leaving the reference unfulfilled.
3971
+ request.pendingChunks--;
3972
+ }
3973
+
3945
3974
function flushCompletedChunks (
3946
3975
request : Request ,
3947
3976
destination : Destination ,
@@ -4087,12 +4116,6 @@ export function abort(request: Request, reason: mixed): void {
4087
4116
}
4088
4117
const abortableTasks = request . abortableTasks ;
4089
4118
if ( abortableTasks . size > 0) {
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
4119
if (
4097
4120
enablePostpone &&
4098
4121
typeof reason === 'object' &&
@@ -4101,10 +4124,20 @@ export function abort(request: Request, reason: mixed): void {
4101
4124
) {
4102
4125
const postponeInstance : Postpone = ( reason : any ) ;
4103
4126
logPostpone ( request , postponeInstance . message , null ) ;
4104
- if ( ! enableHalt || request . type === PRERENDER ) {
4105
- // When prerendering with halt semantics we omit the referred to postpone.
4127
+ if ( enableHalt && request . type === PRERENDER ) {
4128
+ // When prerendering with halt semantics we simply halt the task
4129
+ // and leave the reference unfulfilled.
4130
+ abortableTasks. forEach ( task => haltTask ( task , request ) ) ;
4131
+ abortableTasks . clear ( ) ;
4132
+ } else {
4133
+ // When rendering we produce a shared postpone chunk and then
4134
+ // fulfill each task with a reference to that chunk.
4135
+ const errorId = request . nextChunkId ++ ;
4136
+ request . fatalError = errorId ;
4106
4137
request . pendingChunks ++ ;
4107
4138
emitPostponeChunk ( request , errorId , postponeInstance ) ;
4139
+ abortableTasks . forEach ( task => abortTask ( task , request , errorId ) ) ;
4140
+ abortableTasks . clear ( ) ;
4108
4141
}
4109
4142
} else {
4110
4143
const error =
@@ -4120,14 +4153,22 @@ export function abort(request: Request, reason: mixed): void {
4120
4153
)
4121
4154
: reason ;
4122
4155
const digest = logRecoverableError ( request , error , null ) ;
4123
- if ( ! enableHalt || request . type === RENDER ) {
4124
- // When prerendering with halt semantics we omit the referred to error.
4156
+ if ( enableHalt && request . type === PRERENDER ) {
4157
+ // When prerendering with halt semantics we simply halt the task
4158
+ // and leave the reference unfulfilled.
4159
+ abortableTasks . forEach ( task => haltTask ( task , request ) ) ;
4160
+ abortableTasks . clear ( ) ;
4161
+ } else {
4162
+ // When rendering we produce a shared error chunk and then
4163
+ // fulfill each task with a reference to that chunk.
4164
+ const errorId = request . nextChunkId ++ ;
4165
+ request . fatalError = errorId ;
4125
4166
request . pendingChunks ++ ;
4126
4167
emitErrorChunk ( request , errorId , digest , error ) ;
4168
+ abortableTasks . forEach ( task => abortTask ( task , request , errorId ) ) ;
4169
+ abortableTasks . clear ( ) ;
4127
4170
}
4128
4171
}
4129
- abortableTasks . forEach ( task => abortTask ( task , request , errorId ) ) ;
4130
- abortableTasks . clear ( ) ;
4131
4172
const onAllReady = request . onAllReady ;
4132
4173
onAllReady ( ) ;
4133
4174
}
0 commit comments