Skip to content

Commit 6bf89d9

Browse files
committed
[Fiber/Fizz] Support AsyncIterable as Children and AsyncGenerator Client Components (#28868)
Stacked on #28849, #28854, #28853. Behind a flag. If you're following along from the side-lines. This is probably not what you think it is. It's NOT a way to get updates to a component over time. The AsyncIterable works like an Iterable already works in React which is how an Array works. I.e. it's a list of children - not the value of a child over time. It also doesn't actually render one component at a time. The way it works is more like awaiting the entire list to become an array and then it shows up. Before that it suspends the parent. To actually get these to display one at a time, you have to opt-in with `<SuspenseList>` to describe how they should appear. That's really the interesting part and that not implemented yet. Additionally, since these are effectively Async Functions and uncached promises, they're not actually fully "supported" on the client yet for the same reason rendering plain Promises and Async Functions aren't. They warn. It's only really useful when paired with RSC that produces instrumented versions of these. Ideally we'd published instrumented helpers to help with map/filter style operations that yield new instrumented AsyncIterables. The way the implementation works basically just relies on unwrapThenable and otherwise works like a plain Iterator. There is one quirk with these that are different than just promises. We ask for a new iterator each time we rerender. This means that upon retry we kick off another iteration which itself might kick off new requests that block iterating further. To solve this and make it actually efficient enough to use on the client we'd need to stash something like a buffer of the previous iteration and maybe iterator on the iterable so that we can continue where we left off or synchronously iterate if we've seen it before. Similar to our `.value` convention on Promises. In Fizz, I had to do a special case because when we render an iterator child we don't actually rerender the parent again like we do in Fiber. However, it's more efficient to just continue on where we left off by reusing the entries from the thenable state from before in that case. DiffTrain build for commit 9f2eebd.
1 parent 5ccb5b7 commit 6bf89d9

File tree

10 files changed

+333
-291
lines changed

10 files changed

+333
-291
lines changed

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react-test-renderer/cjs/ReactTestRenderer-dev.js

+15-9
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<46b519886f2205ba3e56e9120fea4a08>>
10+
* @generated SignedSource<<d6449a2b2f866a42a4d0c4e9ceba56bf>>
1111
*/
1212

1313
'use strict';
@@ -131,6 +131,7 @@ var enableSchedulingProfiler = true;
131131
var enableProfilerTimer = true;
132132
var enableProfilerCommitHooks = true;
133133
var enableProfilerNestedUpdatePhase = true;
134+
var enableAsyncIterableChildren = false;
134135
var syncLaneExpirationMs = 250;
135136
var transitionLaneExpirationMs = 5000;
136137
var enableLazyContextPropagation = false;
@@ -5751,7 +5752,7 @@ function createChildReconciler(shouldTrackSideEffects) {
57515752
}
57525753
}
57535754

5754-
if (isArray(newChild) || getIteratorFn(newChild)) {
5755+
if (isArray(newChild) || getIteratorFn(newChild) || enableAsyncIterableChildren ) {
57555756
var _created3 = createFiberFromFragment(newChild, returnFiber.mode, lanes, null);
57565757

57575758
_created3.return = returnFiber;
@@ -5836,7 +5837,7 @@ function createChildReconciler(shouldTrackSideEffects) {
58365837
}
58375838
}
58385839

5839-
if (isArray(newChild) || getIteratorFn(newChild)) {
5840+
if (isArray(newChild) || getIteratorFn(newChild) || enableAsyncIterableChildren ) {
58405841
if (key !== null) {
58415842
return null;
58425843
}
@@ -5904,7 +5905,7 @@ function createChildReconciler(shouldTrackSideEffects) {
59045905
return updateFromMap(existingChildren, returnFiber, newIdx, init(payload), lanes, mergeDebugInfo(debugInfo, newChild._debugInfo));
59055906
}
59065907

5907-
if (isArray(newChild) || getIteratorFn(newChild)) {
5908+
if (isArray(newChild) || getIteratorFn(newChild) || enableAsyncIterableChildren ) {
59085909
var _matchedFiber3 = existingChildren.get(newIdx) || null;
59095910

59105911
return updateFragment(returnFiber, _matchedFiber3, newChild, lanes, null, mergeDebugInfo(debugInfo, newChild._debugInfo));
@@ -6137,7 +6138,7 @@ function createChildReconciler(shouldTrackSideEffects) {
61376138
return resultingFirstChild;
61386139
}
61396140

6140-
function reconcileChildrenIterator(returnFiber, currentFirstChild, newChildrenIterable, lanes, debugInfo) {
6141+
function reconcileChildrenIteratable(returnFiber, currentFirstChild, newChildrenIterable, lanes, debugInfo) {
61416142
// This is the same implementation as reconcileChildrenArray(),
61426143
// but using the iterator instead.
61436144
var iteratorFn = getIteratorFn(newChildrenIterable);
@@ -6176,6 +6177,10 @@ function createChildReconciler(shouldTrackSideEffects) {
61766177
}
61776178
}
61786179

6180+
return reconcileChildrenIterator(returnFiber, currentFirstChild, newChildren, lanes, debugInfo);
6181+
}
6182+
6183+
function reconcileChildrenIterator(returnFiber, currentFirstChild, newChildren, lanes, debugInfo) {
61796184
if (newChildren == null) {
61806185
throw new Error('An iterable object provided no iterator.');
61816186
}
@@ -6481,8 +6486,8 @@ function createChildReconciler(shouldTrackSideEffects) {
64816486
}
64826487

64836488
if (getIteratorFn(newChild)) {
6484-
return reconcileChildrenIterator(returnFiber, currentFirstChild, newChild, lanes, mergeDebugInfo(debugInfo, newChild._debugInfo));
6485-
} // Usables are a valid React node type. When React encounters a Usable in
6489+
return reconcileChildrenIteratable(returnFiber, currentFirstChild, newChild, lanes, mergeDebugInfo(debugInfo, newChild._debugInfo));
6490+
}
64866491
// a child position, it unwraps it using the same algorithm as `use`. For
64876492
// example, for promises, React will throw an exception to unwind the
64886493
// stack, then replay the component once the promise resolves.
@@ -6967,7 +6972,8 @@ function warnIfAsyncClientComponent(Component) {
69676972
// for transpiled async functions. Neither mechanism is completely
69686973
// bulletproof but together they cover the most common cases.
69696974
var isAsyncFunction = // $FlowIgnore[method-unbinding]
6970-
Object.prototype.toString.call(Component) === '[object AsyncFunction]';
6975+
Object.prototype.toString.call(Component) === '[object AsyncFunction]' || // $FlowIgnore[method-unbinding]
6976+
Object.prototype.toString.call(Component) === '[object AsyncGeneratorFunction]';
69716977

69726978
if (isAsyncFunction) {
69736979
// Encountered an async Client Component. This is not yet supported.
@@ -22983,7 +22989,7 @@ identifierPrefix, onUncaughtError, onCaughtError, onRecoverableError, transition
2298322989
return root;
2298422990
}
2298522991

22986-
var ReactVersion = '19.0.0-canary-faeca3c1';
22992+
var ReactVersion = '19.0.0-canary-88928511';
2298722993

2298822994
/*
2298922995
* The `'' + value` pattern (used in perf-sensitive code) throws for Symbol

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react-test-renderer/cjs/ReactTestRenderer-prod.js

+40-36
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<9553b8ed4acfe1f92cf2f7113b446768>>
10+
* @generated SignedSource<<c6fc72b49d174b33d4572246c49e569a>>
1111
*/
1212

1313
"use strict";
@@ -1811,25 +1811,20 @@ function createChildReconciler(shouldTrackSideEffects) {
18111811
function reconcileChildrenIterator(
18121812
returnFiber,
18131813
currentFirstChild,
1814-
newChildrenIterable,
1814+
newChildren,
18151815
lanes
18161816
) {
1817-
var iteratorFn = getIteratorFn(newChildrenIterable);
1818-
if ("function" !== typeof iteratorFn)
1819-
throw Error(
1820-
"An object is not an iterable. This error is likely caused by a bug in React. Please file an issue."
1821-
);
1822-
newChildrenIterable = iteratorFn.call(newChildrenIterable);
1823-
if (null == newChildrenIterable)
1817+
if (null == newChildren)
18241818
throw Error("An iterable object provided no iterator.");
18251819
for (
1826-
var previousNewFiber = (iteratorFn = null),
1820+
var resultingFirstChild = null,
1821+
previousNewFiber = null,
18271822
oldFiber = currentFirstChild,
18281823
newIdx = (currentFirstChild = 0),
18291824
nextOldFiber = null,
1830-
step = newChildrenIterable.next();
1825+
step = newChildren.next();
18311826
null !== oldFiber && !step.done;
1832-
newIdx++, step = newChildrenIterable.next(), null
1827+
newIdx++, step = newChildren.next(), null
18331828
) {
18341829
oldFiber.index > newIdx
18351830
? ((nextOldFiber = oldFiber), (oldFiber = null))
@@ -1845,28 +1840,30 @@ function createChildReconciler(shouldTrackSideEffects) {
18451840
deleteChild(returnFiber, oldFiber);
18461841
currentFirstChild = placeChild(newFiber, currentFirstChild, newIdx);
18471842
null === previousNewFiber
1848-
? (iteratorFn = newFiber)
1843+
? (resultingFirstChild = newFiber)
18491844
: (previousNewFiber.sibling = newFiber);
18501845
previousNewFiber = newFiber;
18511846
oldFiber = nextOldFiber;
18521847
}
18531848
if (step.done)
1854-
return deleteRemainingChildren(returnFiber, oldFiber), iteratorFn;
1849+
return (
1850+
deleteRemainingChildren(returnFiber, oldFiber), resultingFirstChild
1851+
);
18551852
if (null === oldFiber) {
1856-
for (; !step.done; newIdx++, step = newChildrenIterable.next(), null)
1853+
for (; !step.done; newIdx++, step = newChildren.next(), null)
18571854
(step = createChild(returnFiber, step.value, lanes)),
18581855
null !== step &&
18591856
((currentFirstChild = placeChild(step, currentFirstChild, newIdx)),
18601857
null === previousNewFiber
1861-
? (iteratorFn = step)
1858+
? (resultingFirstChild = step)
18621859
: (previousNewFiber.sibling = step),
18631860
(previousNewFiber = step));
1864-
return iteratorFn;
1861+
return resultingFirstChild;
18651862
}
18661863
for (
18671864
oldFiber = mapRemainingChildren(oldFiber);
18681865
!step.done;
1869-
newIdx++, step = newChildrenIterable.next(), null
1866+
newIdx++, step = newChildren.next(), null
18701867
)
18711868
(step = updateFromMap(oldFiber, returnFiber, newIdx, step.value, lanes)),
18721869
null !== step &&
@@ -1875,14 +1872,14 @@ function createChildReconciler(shouldTrackSideEffects) {
18751872
oldFiber.delete(null === step.key ? newIdx : step.key),
18761873
(currentFirstChild = placeChild(step, currentFirstChild, newIdx)),
18771874
null === previousNewFiber
1878-
? (iteratorFn = step)
1875+
? (resultingFirstChild = step)
18791876
: (previousNewFiber.sibling = step),
18801877
(previousNewFiber = step));
18811878
shouldTrackSideEffects &&
18821879
oldFiber.forEach(function (child) {
18831880
return deleteChild(returnFiber, child);
18841881
});
1885-
return iteratorFn;
1882+
return resultingFirstChild;
18861883
}
18871884
function reconcileChildFibersImpl(
18881885
returnFiber,
@@ -2009,13 +2006,20 @@ function createChildReconciler(shouldTrackSideEffects) {
20092006
newChild,
20102007
lanes
20112008
);
2012-
if (getIteratorFn(newChild))
2009+
if (getIteratorFn(newChild)) {
2010+
key = getIteratorFn(newChild);
2011+
if ("function" !== typeof key)
2012+
throw Error(
2013+
"An object is not an iterable. This error is likely caused by a bug in React. Please file an issue."
2014+
);
2015+
newChild = key.call(newChild);
20132016
return reconcileChildrenIterator(
20142017
returnFiber,
20152018
currentFirstChild,
20162019
newChild,
20172020
lanes
20182021
);
2022+
}
20192023
if ("function" === typeof newChild.then)
20202024
return reconcileChildFibersImpl(
20212025
returnFiber,
@@ -9143,19 +9147,19 @@ function wrapFiber(fiber) {
91439147
fiberToWrapper.set(fiber, wrapper));
91449148
return wrapper;
91459149
}
9146-
var devToolsConfig$jscomp$inline_1019 = {
9150+
var devToolsConfig$jscomp$inline_1028 = {
91479151
findFiberByHostInstance: function () {
91489152
throw Error("TestRenderer does not support findFiberByHostInstance()");
91499153
},
91509154
bundleType: 0,
9151-
version: "19.0.0-canary-926deca4",
9155+
version: "19.0.0-canary-e1a9c38c",
91529156
rendererPackageName: "react-test-renderer"
91539157
};
9154-
var internals$jscomp$inline_1238 = {
9155-
bundleType: devToolsConfig$jscomp$inline_1019.bundleType,
9156-
version: devToolsConfig$jscomp$inline_1019.version,
9157-
rendererPackageName: devToolsConfig$jscomp$inline_1019.rendererPackageName,
9158-
rendererConfig: devToolsConfig$jscomp$inline_1019.rendererConfig,
9158+
var internals$jscomp$inline_1247 = {
9159+
bundleType: devToolsConfig$jscomp$inline_1028.bundleType,
9160+
version: devToolsConfig$jscomp$inline_1028.version,
9161+
rendererPackageName: devToolsConfig$jscomp$inline_1028.rendererPackageName,
9162+
rendererConfig: devToolsConfig$jscomp$inline_1028.rendererConfig,
91599163
overrideHookState: null,
91609164
overrideHookStateDeletePath: null,
91619165
overrideHookStateRenamePath: null,
@@ -9172,26 +9176,26 @@ var internals$jscomp$inline_1238 = {
91729176
return null === fiber ? null : fiber.stateNode;
91739177
},
91749178
findFiberByHostInstance:
9175-
devToolsConfig$jscomp$inline_1019.findFiberByHostInstance ||
9179+
devToolsConfig$jscomp$inline_1028.findFiberByHostInstance ||
91769180
emptyFindFiberByHostInstance,
91779181
findHostInstancesForRefresh: null,
91789182
scheduleRefresh: null,
91799183
scheduleRoot: null,
91809184
setRefreshHandler: null,
91819185
getCurrentFiber: null,
9182-
reconcilerVersion: "19.0.0-canary-926deca4"
9186+
reconcilerVersion: "19.0.0-canary-e1a9c38c"
91839187
};
91849188
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
9185-
var hook$jscomp$inline_1239 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
9189+
var hook$jscomp$inline_1248 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
91869190
if (
9187-
!hook$jscomp$inline_1239.isDisabled &&
9188-
hook$jscomp$inline_1239.supportsFiber
9191+
!hook$jscomp$inline_1248.isDisabled &&
9192+
hook$jscomp$inline_1248.supportsFiber
91899193
)
91909194
try {
9191-
(rendererID = hook$jscomp$inline_1239.inject(
9192-
internals$jscomp$inline_1238
9195+
(rendererID = hook$jscomp$inline_1248.inject(
9196+
internals$jscomp$inline_1247
91939197
)),
9194-
(injectedHook = hook$jscomp$inline_1239);
9198+
(injectedHook = hook$jscomp$inline_1248);
91959199
} catch (err) {}
91969200
}
91979201
exports._Scheduler = Scheduler;

0 commit comments

Comments
 (0)