);
+ });
});
diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js
index 33199490bd620..a4b659807ce9e 100644
--- a/packages/react-server/src/ReactFlightServer.js
+++ b/packages/react-server/src/ReactFlightServer.js
@@ -350,10 +350,11 @@ const PENDING = 0;
const COMPLETED = 1;
const ABORTED = 3;
const ERRORED = 4;
+const RENDERING = 5;
type Task = {
id: number,
- status: 0 | 1 | 3 | 4,
+ status: 0 | 1 | 3 | 4 | 5,
model: ReactClientValue,
ping: () => void,
toJSON: (key: string, value: ReactClientValue) => ReactJSONValue,
@@ -1015,6 +1016,16 @@ function renderFunctionComponent(
const secondArg = undefined;
result = Component(props, secondArg);
}
+
+ if (task.status === ABORTED) {
+ // If we aborted during rendering we should interrupt the render but
+ // we don't need to provide an error because the renderer will encode
+ // the abort error as the reason.
+
+ // eslint-disable-next-line no-throw-literal
+ throw null;
+ }
+
if (
typeof result === 'object' &&
result !== null &&
@@ -1880,11 +1891,19 @@ function renderModel(
key: string,
value: ReactClientValue,
): ReactJSONValue {
+ if (task.status === ABORTED) {
+ const errorId = task.model;
+ return serializeLazyID(errorId);
+ }
const prevKeyPath = task.keyPath;
const prevImplicitSlot = task.implicitSlot;
try {
return renderModelDestructive(request, task, parent, key, value);
} catch (thrownValue) {
+ if (task.status === ABORTED) {
+ const errorId = task.model;
+ return serializeLazyID(errorId);
+ }
const x =
thrownValue === SuspenseException
? // This is a special type of exception used for Suspense. For historical
@@ -3207,6 +3226,7 @@ function retryTask(request: Request, task: Task): void {
}
const prevDebugID = debugID;
+ task.status = RENDERING;
try {
// Track the root so we know that we have to emit this object even though it
@@ -3277,6 +3297,7 @@ function retryTask(request: Request, task: Task): void {
const ping = task.ping;
x.then(ping, ping);
task.thenableState = getThenableStateAfterSuspending();
+ task.status = PENDING;
return;
} else if (enablePostpone && x.$$typeof === REACT_POSTPONE_TYPE) {
request.abortableTasks.delete(task);
@@ -3344,12 +3365,20 @@ function performWork(request: Request): void {
}
function abortTask(task: Task, request: Request, errorId: number): void {
- task.status = ABORTED;
- // Instead of emitting an error per task.id, we emit a model that only
- // has a single value referencing the error.
- const ref = serializeByValueID(errorId);
- const processedChunk = encodeReferenceChunk(request, task.id, ref);
- request.completedErrorChunks.push(processedChunk);
+ if (task.status === RENDERING) {
+ task.status = ABORTED;
+ task.model = errorId;
+ // We don't direclty abort rendering tasks here but we mutate the model to
+ // encode the errorId and mark the task aborted. The abort will be handled
+ // by the task itself when control is returned.
+ } else {
+ task.status = ABORTED;
+ // Instead of emitting an error per task.id, we emit a model that only
+ // has a single value referencing the error.
+ const ref = serializeByValueID(errorId);
+ const processedChunk = encodeReferenceChunk(request, task.id, ref);
+ request.completedErrorChunks.push(processedChunk);
+ }
}
function flushCompletedChunks(