Skip to content

Commit f007da7

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 [9f2eebd](9f2eebd)
1 parent d1d0508 commit f007da7

28 files changed

+631
-496
lines changed

compiled/facebook-www/REVISION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3b551c82844bcfde51f0febb8e42c1a0d777df2c
1+
9f2eebd807bf53b7d9901cf0b768762948224cae

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

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ function _assertThisInitialized(self) {
6363
return self;
6464
}
6565

66-
var ReactVersion = '19.0.0-www-classic-5033c724';
66+
var ReactVersion = '19.0.0-www-classic-bd72d7f5';
6767

6868
var LegacyRoot = 0;
6969
var ConcurrentRoot = 1;
@@ -169,6 +169,7 @@ var enableProfilerNestedUpdatePhase = true;
169169
var enableAsyncActions = true;
170170

171171
var enableSchedulingProfiler = dynamicFeatureFlags.enableSchedulingProfiler;
172+
var enableAsyncIterableChildren = false;
172173
var disableLegacyMode = false;
173174

174175
var FunctionComponent = 0;
@@ -6597,7 +6598,7 @@ function createChildReconciler(shouldTrackSideEffects) {
65976598
}
65986599
}
65996600

6600-
if (isArray(newChild) || getIteratorFn(newChild)) {
6601+
if (isArray(newChild) || getIteratorFn(newChild) || enableAsyncIterableChildren ) {
66016602
var _created3 = createFiberFromFragment(newChild, returnFiber.mode, lanes, null);
66026603

66036604
_created3.return = returnFiber;
@@ -6682,7 +6683,7 @@ function createChildReconciler(shouldTrackSideEffects) {
66826683
}
66836684
}
66846685

6685-
if (isArray(newChild) || getIteratorFn(newChild)) {
6686+
if (isArray(newChild) || getIteratorFn(newChild) || enableAsyncIterableChildren ) {
66866687
if (key !== null) {
66876688
return null;
66886689
}
@@ -6750,7 +6751,7 @@ function createChildReconciler(shouldTrackSideEffects) {
67506751
return updateFromMap(existingChildren, returnFiber, newIdx, init(payload), lanes, mergeDebugInfo(debugInfo, newChild._debugInfo));
67516752
}
67526753

6753-
if (isArray(newChild) || getIteratorFn(newChild)) {
6754+
if (isArray(newChild) || getIteratorFn(newChild) || enableAsyncIterableChildren ) {
67546755
var _matchedFiber3 = existingChildren.get(newIdx) || null;
67556756

67566757
return updateFragment(returnFiber, _matchedFiber3, newChild, lanes, null, mergeDebugInfo(debugInfo, newChild._debugInfo));
@@ -6983,7 +6984,7 @@ function createChildReconciler(shouldTrackSideEffects) {
69836984
return resultingFirstChild;
69846985
}
69856986

6986-
function reconcileChildrenIterator(returnFiber, currentFirstChild, newChildrenIterable, lanes, debugInfo) {
6987+
function reconcileChildrenIteratable(returnFiber, currentFirstChild, newChildrenIterable, lanes, debugInfo) {
69876988
// This is the same implementation as reconcileChildrenArray(),
69886989
// but using the iterator instead.
69896990
var iteratorFn = getIteratorFn(newChildrenIterable);
@@ -7022,6 +7023,10 @@ function createChildReconciler(shouldTrackSideEffects) {
70227023
}
70237024
}
70247025

7026+
return reconcileChildrenIterator(returnFiber, currentFirstChild, newChildren, lanes, debugInfo);
7027+
}
7028+
7029+
function reconcileChildrenIterator(returnFiber, currentFirstChild, newChildren, lanes, debugInfo) {
70257030
if (newChildren == null) {
70267031
throw new Error('An iterable object provided no iterator.');
70277032
}
@@ -7327,8 +7332,8 @@ function createChildReconciler(shouldTrackSideEffects) {
73277332
}
73287333

73297334
if (getIteratorFn(newChild)) {
7330-
return reconcileChildrenIterator(returnFiber, currentFirstChild, newChild, lanes, mergeDebugInfo(debugInfo, newChild._debugInfo));
7331-
} // Usables are a valid React node type. When React encounters a Usable in
7335+
return reconcileChildrenIteratable(returnFiber, currentFirstChild, newChild, lanes, mergeDebugInfo(debugInfo, newChild._debugInfo));
7336+
}
73327337
// a child position, it unwraps it using the same algorithm as `use`. For
73337338
// example, for promises, React will throw an exception to unwind the
73347339
// stack, then replay the component once the promise resolves.
@@ -7836,7 +7841,8 @@ function warnIfAsyncClientComponent(Component) {
78367841
// for transpiled async functions. Neither mechanism is completely
78377842
// bulletproof but together they cover the most common cases.
78387843
var isAsyncFunction = // $FlowIgnore[method-unbinding]
7839-
Object.prototype.toString.call(Component) === '[object AsyncFunction]';
7844+
Object.prototype.toString.call(Component) === '[object AsyncFunction]' || // $FlowIgnore[method-unbinding]
7845+
Object.prototype.toString.call(Component) === '[object AsyncGeneratorFunction]';
78407846

78417847
if (isAsyncFunction) {
78427848
// Encountered an async Client Component. This is not yet supported.

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

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ function _assertThisInitialized(self) {
6363
return self;
6464
}
6565

66-
var ReactVersion = '19.0.0-www-modern-9c7c2ced';
66+
var ReactVersion = '19.0.0-www-modern-03c6d7dd';
6767

6868
var LegacyRoot = 0;
6969
var ConcurrentRoot = 1;
@@ -169,6 +169,7 @@ var enableProfilerNestedUpdatePhase = true;
169169
var enableAsyncActions = true;
170170

171171
var enableSchedulingProfiler = dynamicFeatureFlags.enableSchedulingProfiler;
172+
var enableAsyncIterableChildren = false;
172173
var disableLegacyMode = true;
173174

174175
var FunctionComponent = 0;
@@ -6386,7 +6387,7 @@ function createChildReconciler(shouldTrackSideEffects) {
63866387
}
63876388
}
63886389

6389-
if (isArray(newChild) || getIteratorFn(newChild)) {
6390+
if (isArray(newChild) || getIteratorFn(newChild) || enableAsyncIterableChildren ) {
63906391
var _created3 = createFiberFromFragment(newChild, returnFiber.mode, lanes, null);
63916392

63926393
_created3.return = returnFiber;
@@ -6471,7 +6472,7 @@ function createChildReconciler(shouldTrackSideEffects) {
64716472
}
64726473
}
64736474

6474-
if (isArray(newChild) || getIteratorFn(newChild)) {
6475+
if (isArray(newChild) || getIteratorFn(newChild) || enableAsyncIterableChildren ) {
64756476
if (key !== null) {
64766477
return null;
64776478
}
@@ -6539,7 +6540,7 @@ function createChildReconciler(shouldTrackSideEffects) {
65396540
return updateFromMap(existingChildren, returnFiber, newIdx, init(payload), lanes, mergeDebugInfo(debugInfo, newChild._debugInfo));
65406541
}
65416542

6542-
if (isArray(newChild) || getIteratorFn(newChild)) {
6543+
if (isArray(newChild) || getIteratorFn(newChild) || enableAsyncIterableChildren ) {
65436544
var _matchedFiber3 = existingChildren.get(newIdx) || null;
65446545

65456546
return updateFragment(returnFiber, _matchedFiber3, newChild, lanes, null, mergeDebugInfo(debugInfo, newChild._debugInfo));
@@ -6772,7 +6773,7 @@ function createChildReconciler(shouldTrackSideEffects) {
67726773
return resultingFirstChild;
67736774
}
67746775

6775-
function reconcileChildrenIterator(returnFiber, currentFirstChild, newChildrenIterable, lanes, debugInfo) {
6776+
function reconcileChildrenIteratable(returnFiber, currentFirstChild, newChildrenIterable, lanes, debugInfo) {
67766777
// This is the same implementation as reconcileChildrenArray(),
67776778
// but using the iterator instead.
67786779
var iteratorFn = getIteratorFn(newChildrenIterable);
@@ -6811,6 +6812,10 @@ function createChildReconciler(shouldTrackSideEffects) {
68116812
}
68126813
}
68136814

6815+
return reconcileChildrenIterator(returnFiber, currentFirstChild, newChildren, lanes, debugInfo);
6816+
}
6817+
6818+
function reconcileChildrenIterator(returnFiber, currentFirstChild, newChildren, lanes, debugInfo) {
68146819
if (newChildren == null) {
68156820
throw new Error('An iterable object provided no iterator.');
68166821
}
@@ -7116,8 +7121,8 @@ function createChildReconciler(shouldTrackSideEffects) {
71167121
}
71177122

71187123
if (getIteratorFn(newChild)) {
7119-
return reconcileChildrenIterator(returnFiber, currentFirstChild, newChild, lanes, mergeDebugInfo(debugInfo, newChild._debugInfo));
7120-
} // Usables are a valid React node type. When React encounters a Usable in
7124+
return reconcileChildrenIteratable(returnFiber, currentFirstChild, newChild, lanes, mergeDebugInfo(debugInfo, newChild._debugInfo));
7125+
}
71217126
// a child position, it unwraps it using the same algorithm as `use`. For
71227127
// example, for promises, React will throw an exception to unwind the
71237128
// stack, then replay the component once the promise resolves.
@@ -7625,7 +7630,8 @@ function warnIfAsyncClientComponent(Component) {
76257630
// for transpiled async functions. Neither mechanism is completely
76267631
// bulletproof but together they cover the most common cases.
76277632
var isAsyncFunction = // $FlowIgnore[method-unbinding]
7628-
Object.prototype.toString.call(Component) === '[object AsyncFunction]';
7633+
Object.prototype.toString.call(Component) === '[object AsyncFunction]' || // $FlowIgnore[method-unbinding]
7634+
Object.prototype.toString.call(Component) === '[object AsyncGeneratorFunction]';
76297635

76307636
if (isAsyncFunction) {
76317637
// Encountered an async Client Component. This is not yet supported.

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

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2204,22 +2204,19 @@ function createChildReconciler(shouldTrackSideEffects) {
22042204
function reconcileChildrenIterator(
22052205
returnFiber,
22062206
currentFirstChild,
2207-
newChildrenIterable,
2207+
newChildren,
22082208
lanes
22092209
) {
2210-
var iteratorFn = getIteratorFn(newChildrenIterable);
2211-
if ("function" !== typeof iteratorFn)
2212-
throw Error(formatProdErrorMessage(150));
2213-
newChildrenIterable = iteratorFn.call(newChildrenIterable);
2214-
if (null == newChildrenIterable) throw Error(formatProdErrorMessage(151));
2210+
if (null == newChildren) throw Error(formatProdErrorMessage(151));
22152211
for (
2216-
var previousNewFiber = (iteratorFn = null),
2212+
var resultingFirstChild = null,
2213+
previousNewFiber = null,
22172214
oldFiber = currentFirstChild,
22182215
newIdx = (currentFirstChild = 0),
22192216
nextOldFiber = null,
2220-
step = newChildrenIterable.next();
2217+
step = newChildren.next();
22212218
null !== oldFiber && !step.done;
2222-
newIdx++, step = newChildrenIterable.next(), null
2219+
newIdx++, step = newChildren.next(), null
22232220
) {
22242221
oldFiber.index > newIdx
22252222
? ((nextOldFiber = oldFiber), (oldFiber = null))
@@ -2235,28 +2232,30 @@ function createChildReconciler(shouldTrackSideEffects) {
22352232
deleteChild(returnFiber, oldFiber);
22362233
currentFirstChild = placeChild(newFiber, currentFirstChild, newIdx);
22372234
null === previousNewFiber
2238-
? (iteratorFn = newFiber)
2235+
? (resultingFirstChild = newFiber)
22392236
: (previousNewFiber.sibling = newFiber);
22402237
previousNewFiber = newFiber;
22412238
oldFiber = nextOldFiber;
22422239
}
22432240
if (step.done)
2244-
return deleteRemainingChildren(returnFiber, oldFiber), iteratorFn;
2241+
return (
2242+
deleteRemainingChildren(returnFiber, oldFiber), resultingFirstChild
2243+
);
22452244
if (null === oldFiber) {
2246-
for (; !step.done; newIdx++, step = newChildrenIterable.next(), null)
2245+
for (; !step.done; newIdx++, step = newChildren.next(), null)
22472246
(step = createChild(returnFiber, step.value, lanes)),
22482247
null !== step &&
22492248
((currentFirstChild = placeChild(step, currentFirstChild, newIdx)),
22502249
null === previousNewFiber
2251-
? (iteratorFn = step)
2250+
? (resultingFirstChild = step)
22522251
: (previousNewFiber.sibling = step),
22532252
(previousNewFiber = step));
2254-
return iteratorFn;
2253+
return resultingFirstChild;
22552254
}
22562255
for (
22572256
oldFiber = mapRemainingChildren(oldFiber);
22582257
!step.done;
2259-
newIdx++, step = newChildrenIterable.next(), null
2258+
newIdx++, step = newChildren.next(), null
22602259
)
22612260
(step = updateFromMap(oldFiber, returnFiber, newIdx, step.value, lanes)),
22622261
null !== step &&
@@ -2265,14 +2264,14 @@ function createChildReconciler(shouldTrackSideEffects) {
22652264
oldFiber.delete(null === step.key ? newIdx : step.key),
22662265
(currentFirstChild = placeChild(step, currentFirstChild, newIdx)),
22672266
null === previousNewFiber
2268-
? (iteratorFn = step)
2267+
? (resultingFirstChild = step)
22692268
: (previousNewFiber.sibling = step),
22702269
(previousNewFiber = step));
22712270
shouldTrackSideEffects &&
22722271
oldFiber.forEach(function (child) {
22732272
return deleteChild(returnFiber, child);
22742273
});
2275-
return iteratorFn;
2274+
return resultingFirstChild;
22762275
}
22772276
function reconcileChildFibersImpl(
22782277
returnFiber,
@@ -2404,13 +2403,18 @@ function createChildReconciler(shouldTrackSideEffects) {
24042403
newChild,
24052404
lanes
24062405
);
2407-
if (getIteratorFn(newChild))
2406+
if (getIteratorFn(newChild)) {
2407+
child = getIteratorFn(newChild);
2408+
if ("function" !== typeof child)
2409+
throw Error(formatProdErrorMessage(150));
2410+
newChild = child.call(newChild);
24082411
return reconcileChildrenIterator(
24092412
returnFiber,
24102413
currentFirstChild,
24112414
newChild,
24122415
lanes
24132416
);
2417+
}
24142418
if ("function" === typeof newChild.then)
24152419
return reconcileChildFibersImpl(
24162420
returnFiber,
@@ -10619,19 +10623,19 @@ var slice = Array.prototype.slice,
1061910623
};
1062010624
return Text;
1062110625
})(React.Component),
10622-
devToolsConfig$jscomp$inline_1114 = {
10626+
devToolsConfig$jscomp$inline_1123 = {
1062310627
findFiberByHostInstance: function () {
1062410628
return null;
1062510629
},
1062610630
bundleType: 0,
10627-
version: "19.0.0-www-classic-48f6e4f0",
10631+
version: "19.0.0-www-classic-6eb34c6f",
1062810632
rendererPackageName: "react-art"
1062910633
};
10630-
var internals$jscomp$inline_1322 = {
10631-
bundleType: devToolsConfig$jscomp$inline_1114.bundleType,
10632-
version: devToolsConfig$jscomp$inline_1114.version,
10633-
rendererPackageName: devToolsConfig$jscomp$inline_1114.rendererPackageName,
10634-
rendererConfig: devToolsConfig$jscomp$inline_1114.rendererConfig,
10634+
var internals$jscomp$inline_1331 = {
10635+
bundleType: devToolsConfig$jscomp$inline_1123.bundleType,
10636+
version: devToolsConfig$jscomp$inline_1123.version,
10637+
rendererPackageName: devToolsConfig$jscomp$inline_1123.rendererPackageName,
10638+
rendererConfig: devToolsConfig$jscomp$inline_1123.rendererConfig,
1063510639
overrideHookState: null,
1063610640
overrideHookStateDeletePath: null,
1063710641
overrideHookStateRenamePath: null,
@@ -10648,26 +10652,26 @@ var internals$jscomp$inline_1322 = {
1064810652
return null === fiber ? null : fiber.stateNode;
1064910653
},
1065010654
findFiberByHostInstance:
10651-
devToolsConfig$jscomp$inline_1114.findFiberByHostInstance ||
10655+
devToolsConfig$jscomp$inline_1123.findFiberByHostInstance ||
1065210656
emptyFindFiberByHostInstance,
1065310657
findHostInstancesForRefresh: null,
1065410658
scheduleRefresh: null,
1065510659
scheduleRoot: null,
1065610660
setRefreshHandler: null,
1065710661
getCurrentFiber: null,
10658-
reconcilerVersion: "19.0.0-www-classic-48f6e4f0"
10662+
reconcilerVersion: "19.0.0-www-classic-6eb34c6f"
1065910663
};
1066010664
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
10661-
var hook$jscomp$inline_1323 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
10665+
var hook$jscomp$inline_1332 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
1066210666
if (
10663-
!hook$jscomp$inline_1323.isDisabled &&
10664-
hook$jscomp$inline_1323.supportsFiber
10667+
!hook$jscomp$inline_1332.isDisabled &&
10668+
hook$jscomp$inline_1332.supportsFiber
1066510669
)
1066610670
try {
10667-
(rendererID = hook$jscomp$inline_1323.inject(
10668-
internals$jscomp$inline_1322
10671+
(rendererID = hook$jscomp$inline_1332.inject(
10672+
internals$jscomp$inline_1331
1066910673
)),
10670-
(injectedHook = hook$jscomp$inline_1323);
10674+
(injectedHook = hook$jscomp$inline_1332);
1067110675
} catch (err) {}
1067210676
}
1067310677
var Path = Mode$1.Path;

0 commit comments

Comments
 (0)