Skip to content

Commit 394c2e6

Browse files
committed
Add Postpone API (#27238)
This adds an experimental `unstable_postpone(reason)` API. Currently we don't have a way to model effectively an Infinite Promise. I.e. something that suspends but never resolves. The reason this is useful is because you might have something else that unblocks it later. E.g. by updating in place later, or by client rendering. On the client this works to model as an Infinite Promise (in fact, that's what this implementation does). However, in Fizz and Flight that doesn't work because the stream needs to end at some point. We don't have any way of knowing that we're suspended on infinite promises. It's not enough to tag the promises because you could await those and thus creating new promises. The only way we really have to signal this through a series of indirections like async functions, is by throwing. It's not 100% safe because these values can be caught but it's the best we can do. Effectively `postpone(reason)` behaves like a built-in [Catch Boundary](facebook/react#26854). It's like `raise(Postpone, reason)` except it's built-in so it needs to be able to be encoded and caught by Suspense boundaries. In Flight and Fizz these behave pretty much the same as errors. Flight just forwards it to retrigger on the client. In Fizz they just trigger client rendering which itself might just postpone again or fill in the value. The difference is how they get logged. In Flight and Fizz they log to `onPostpone(reason)` instead of `onError(error)`. This log is meant to help find deopts on the server like finding places where you fall back to client rendering. The reason that you pass in is for that purpose to help the reason for any deopts. I do track the stack trace in DEV but I don't currently expose it to `onPostpone`. This seems like a limitation. It might be better to expose the Postpone object which is an Error object but that's more of an implementation detail. I could also pass it as a second argument. On the client after hydration they don't get passed to `onRecoverableError`. There's no global `onPostpone` API to capture postponed things on the client just like there's no `onError`. At that point it's just assumed to be intentional. It doesn't have any `digest` or reason passed to the client since it's not logged. There are some hacky solutions that currently just tries to reuse as much of the existing code as possible but should be more properly implemented. - Fiber is currently just converting it to a fake Promise object so that it behaves like an infinite Promise. - Fizz is encoding the magic digest string `"POSTPONE"` in the HTML so we know to ignore it but it should probably just be something neater that doesn't share namespace with digests. Next I plan on using this in the `/static` entry points for additional features. Why "postpone"? It's basically a synonym to "defer" but we plan on using "defer" for other purposes and it's overloaded anyway. DiffTrain build for [ac1a16c67e268fcb2c52e91717cbc918c7c24446](facebook/react@ac1a16c)
1 parent d691a9f commit 394c2e6

24 files changed

+1992
-1844
lines changed

compiled/facebook-www/REVISION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0fb5b61ac6951a492242618e4ace6c1826335efc
1+
ac1a16c67e268fcb2c52e91717cbc918c7c24446

compiled/facebook-www/React-dev.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ if (
2727
}
2828
"use strict";
2929

30-
var ReactVersion = "18.3.0-www-modern-1d530573";
30+
var ReactVersion = "18.3.0-www-modern-00d6344c";
3131

3232
// ATTENTION
3333
// When adding new symbols to this file,

compiled/facebook-www/React-prod.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -615,4 +615,4 @@ exports.useSyncExternalStore = function (
615615
exports.useTransition = function () {
616616
return ReactCurrentDispatcher.current.useTransition();
617617
};
618-
exports.version = "18.3.0-www-modern-8591a478";
618+
exports.version = "18.3.0-www-modern-5d9b9771";

compiled/facebook-www/ReactART-dev.classic.js

Lines changed: 173 additions & 168 deletions
Large diffs are not rendered by default.

compiled/facebook-www/ReactART-dev.modern.js

Lines changed: 173 additions & 168 deletions
Large diffs are not rendered by default.

compiled/facebook-www/ReactART-prod.modern.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9759,7 +9759,7 @@ var slice = Array.prototype.slice,
97599759
return null;
97609760
},
97619761
bundleType: 0,
9762-
version: "18.3.0-www-modern-c25ea5bc",
9762+
version: "18.3.0-www-modern-0328f77f",
97639763
rendererPackageName: "react-art"
97649764
};
97659765
var internals$jscomp$inline_1283 = {
@@ -9790,7 +9790,7 @@ var internals$jscomp$inline_1283 = {
97909790
scheduleRoot: null,
97919791
setRefreshHandler: null,
97929792
getCurrentFiber: null,
9793-
reconcilerVersion: "18.3.0-www-modern-c25ea5bc"
9793+
reconcilerVersion: "18.3.0-www-modern-0328f77f"
97949794
};
97959795
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
97969796
var hook$jscomp$inline_1284 = __REACT_DEVTOOLS_GLOBAL_HOOK__;

compiled/facebook-www/ReactDOM-dev.classic.js

Lines changed: 200 additions & 196 deletions
Large diffs are not rendered by default.

compiled/facebook-www/ReactDOM-dev.modern.js

Lines changed: 200 additions & 196 deletions
Large diffs are not rendered by default.

compiled/facebook-www/ReactDOM-prod.classic.js

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10528,21 +10528,22 @@ function throwAndUnwindWorkLoop(unitOfWork, thrownValue) {
1052810528
renderDidSuspendDelayIfPossible();
1052910529
break a;
1053010530
} else value = Error(formatProdErrorMessage(426));
10531-
} else if (
10532-
isHydrating &&
10533-
unitOfWork.mode & 1 &&
10534-
((wakeable = suspenseHandlerStackCursor.current), null !== wakeable)
10535-
) {
10536-
0 === (wakeable.flags & 65536) && (wakeable.flags |= 256);
10537-
markSuspenseBoundaryShouldCapture(
10538-
wakeable,
10539-
returnFiber,
10540-
unitOfWork,
10541-
root,
10542-
thrownValue
10543-
);
10544-
queueHydrationError(createCapturedValueAtFiber(value, unitOfWork));
10545-
break a;
10531+
}
10532+
if (isHydrating && unitOfWork.mode & 1) {
10533+
var suspenseBoundary$61 = suspenseHandlerStackCursor.current;
10534+
if (null !== suspenseBoundary$61) {
10535+
0 === (suspenseBoundary$61.flags & 65536) &&
10536+
(suspenseBoundary$61.flags |= 256);
10537+
markSuspenseBoundaryShouldCapture(
10538+
suspenseBoundary$61,
10539+
returnFiber,
10540+
unitOfWork,
10541+
root,
10542+
thrownValue
10543+
);
10544+
queueHydrationError(createCapturedValueAtFiber(value, unitOfWork));
10545+
break a;
10546+
}
1054610547
}
1054710548
root = value = createCapturedValueAtFiber(value, unitOfWork);
1054810549
4 !== workInProgressRootExitStatus &&
@@ -16574,7 +16575,7 @@ Internals.Events = [
1657416575
var devToolsConfig$jscomp$inline_1800 = {
1657516576
findFiberByHostInstance: getClosestInstanceFromNode,
1657616577
bundleType: 0,
16577-
version: "18.3.0-www-classic-ac0b40a5",
16578+
version: "18.3.0-www-classic-a80847ca",
1657816579
rendererPackageName: "react-dom"
1657916580
};
1658016581
var internals$jscomp$inline_2159 = {
@@ -16604,7 +16605,7 @@ var internals$jscomp$inline_2159 = {
1660416605
scheduleRoot: null,
1660516606
setRefreshHandler: null,
1660616607
getCurrentFiber: null,
16607-
reconcilerVersion: "18.3.0-www-classic-ac0b40a5"
16608+
reconcilerVersion: "18.3.0-www-classic-a80847ca"
1660816609
};
1660916610
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
1661016611
var hook$jscomp$inline_2160 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
@@ -16832,4 +16833,4 @@ exports.unstable_renderSubtreeIntoContainer = function (
1683216833
);
1683316834
};
1683416835
exports.unstable_runWithPriority = runWithPriority;
16835-
exports.version = "18.3.0-www-classic-ac0b40a5";
16836+
exports.version = "18.3.0-www-classic-a80847ca";

compiled/facebook-www/ReactDOM-prod.modern.js

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10360,21 +10360,22 @@ function throwAndUnwindWorkLoop(unitOfWork, thrownValue) {
1036010360
renderDidSuspendDelayIfPossible();
1036110361
break a;
1036210362
} else value = Error(formatProdErrorMessage(426));
10363-
} else if (
10364-
isHydrating &&
10365-
unitOfWork.mode & 1 &&
10366-
((wakeable = suspenseHandlerStackCursor.current), null !== wakeable)
10367-
) {
10368-
0 === (wakeable.flags & 65536) && (wakeable.flags |= 256);
10369-
markSuspenseBoundaryShouldCapture(
10370-
wakeable,
10371-
returnFiber,
10372-
unitOfWork,
10373-
root,
10374-
thrownValue
10375-
);
10376-
queueHydrationError(createCapturedValueAtFiber(value, unitOfWork));
10377-
break a;
10363+
}
10364+
if (isHydrating && unitOfWork.mode & 1) {
10365+
var suspenseBoundary$61 = suspenseHandlerStackCursor.current;
10366+
if (null !== suspenseBoundary$61) {
10367+
0 === (suspenseBoundary$61.flags & 65536) &&
10368+
(suspenseBoundary$61.flags |= 256);
10369+
markSuspenseBoundaryShouldCapture(
10370+
suspenseBoundary$61,
10371+
returnFiber,
10372+
unitOfWork,
10373+
root,
10374+
thrownValue
10375+
);
10376+
queueHydrationError(createCapturedValueAtFiber(value, unitOfWork));
10377+
break a;
10378+
}
1037810379
}
1037910380
root = value = createCapturedValueAtFiber(value, unitOfWork);
1038010381
4 !== workInProgressRootExitStatus &&
@@ -16104,7 +16105,7 @@ Internals.Events = [
1610416105
var devToolsConfig$jscomp$inline_1759 = {
1610516106
findFiberByHostInstance: getClosestInstanceFromNode,
1610616107
bundleType: 0,
16107-
version: "18.3.0-www-modern-c25ea5bc",
16108+
version: "18.3.0-www-modern-0328f77f",
1610816109
rendererPackageName: "react-dom"
1610916110
};
1611016111
var internals$jscomp$inline_2123 = {
@@ -16135,7 +16136,7 @@ var internals$jscomp$inline_2123 = {
1613516136
scheduleRoot: null,
1613616137
setRefreshHandler: null,
1613716138
getCurrentFiber: null,
16138-
reconcilerVersion: "18.3.0-www-modern-c25ea5bc"
16139+
reconcilerVersion: "18.3.0-www-modern-0328f77f"
1613916140
};
1614016141
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
1614116142
var hook$jscomp$inline_2124 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
@@ -16291,4 +16292,4 @@ exports.unstable_createEventHandle = function (type, options) {
1629116292
return eventHandle;
1629216293
};
1629316294
exports.unstable_runWithPriority = runWithPriority;
16294-
exports.version = "18.3.0-www-modern-c25ea5bc";
16295+
exports.version = "18.3.0-www-modern-0328f77f";

0 commit comments

Comments
 (0)