236
type SuspenseListRow = {
237
pendingTasks: number, // The number of tasks, previous rows and inner suspense boundaries blocking this row.
238
boundaries: null | Array<SuspenseBoundary>, // The boundaries in this row waiting to be unblocked by the previous row. (null means this row is not blocked)
239
+ together: boolean, // All the boundaries within this row must be revealed together.
240
next: null | SuspenseListRow, // The next row blocked by this one.
241
};
242
1405
}
1406
return;
1407
}
1408
+ } else {
1409
+ const boundaryRow = prevRow;
1410
+ if (boundaryRow !== null && boundaryRow.together) {
1411
+ tryToResolveTogetherRow(request, boundaryRow);
1412
+ }
1413
}
1414
} catch (thrownValue: mixed) {
1415
newBoundary.status = CLIENT_RENDERED;
1676
1677
function finishSuspenseListRow(request: Request, row: SuspenseListRow): void {
1678
// This row finished. Now we have to unblock all the next rows that were blocked on this.
1679
+ unblockSuspenseListRow(request, row.next);
1680
+}
1681
+
1682
+function unblockSuspenseListRow(
1683
+ request: Request,
1684
+ unblockedRow: null | SuspenseListRow,
1685
+): void {
1686
// We do this in a loop to avoid stack overflow for very long lists that get unblocked.
1674
- let unblockedRow = row.next;
1687
while (unblockedRow !== null) {
1688
// Unblocking the boundaries will decrement the count of this row but we keep it above
1689
// zero so they never finish this row recursively.
1704
}
1705
}
1706
1707
+function tryToResolveTogetherRow(
1708
+ request: Request,
1709
+ togetherRow: SuspenseListRow,
1710
+): void {
1711
+ // If we have a "together" row and all the pendingTasks are really the boundaries themselves,
1712
+ // and we won't outline any of them then we can unblock this row early so that we can inline
1713
+ // all the boundaries at once.
1714
+ const boundaries = togetherRow.boundaries;
1715
+ if (boundaries === null || togetherRow.pendingTasks !== boundaries.length) {
1716
+ return;
1717
+ }
1718
+ let allCompleteAndInlinable = true;
1719
+ for (let i = 0; i < boundaries.length; i++) {
1720
+ const rowBoundary = boundaries[i];
1721
+ if (
1722
+ rowBoundary.pendingTasks !== 1 ||
1723
+ rowBoundary.parentFlushed ||
1724
+ isEligibleForOutlining(request, rowBoundary)
1725
+ ) {
1726
+ allCompleteAndInlinable = false;
1727
+ break;
1728
+ }
1729
+ }
1730
+ if (allCompleteAndInlinable) {
1731
+ unblockSuspenseListRow(request, togetherRow);
1732
+ }
1733
+}
1734
+
1735
function createSuspenseListRow(
1736
previousRow: null | SuspenseListRow,
1737
): SuspenseListRow {
1738
const newRow: SuspenseListRow = {
1739
pendingTasks: 1, // At first the row is blocked on attempting rendering itself.
1740
boundaries: null,
1741
+ together: false,
1742
next: null,
1743
};
1744
if (previousRow !== null && previousRow.pendingTasks > 0) {
2019
}
2020
2021
if (revealOrder === 'together') {
1981
- // TODO
2022
+ const prevKeyPath = task.keyPath;
2023
+ const prevRow = task.row;
2024
+ const newRow = (task.row = createSuspenseListRow(null));
2025
+ // This will cause boundaries to block on this row, but there's nothing to
2026
+ // unblock them. We'll use the partial flushing pass to unblock them.
2027
+ newRow.boundaries = [];
2028
+ newRow.together = true;
2029
+ task.keyPath = keyPath;
2030
+ renderNodeDestructive(request, task, children, -1);
2031
+ if (--newRow.pendingTasks === 0) {
2032
+ finishSuspenseListRow(request, newRow);
2033
+ }
2034
+ task.keyPath = prevKeyPath;
2035
+ task.row = prevRow;
2036
+ if (prevRow !== null && newRow.pendingTasks > 0) {
2037
+ // If we are part of an outer SuspenseList and our row is still pending, then that blocks
2038
+ // the parent row from completing. We can continue the chain.
2039
+ prevRow.pendingTasks++;
2040
+ newRow.next = prevRow;
2041
+ }
2042
+ return;
2043
}
2044
// For other reveal order modes, we just render it as a fragment.
2045
const prevKeyPath = task.keyPath;
4820
if (row !== null) {
4821
if (--row.pendingTasks === 0) {
4822
finishSuspenseListRow(request, row);
4823
+ } else if (row.together) {
4824
+ tryToResolveTogetherRow(request, row);
4825
}
4826
}
4827
request.allPendingTasks--;
4911
}
4912
}
4913
}
4914
+ const boundaryRow = boundary.row;
4915
+ if (boundaryRow !== null && boundaryRow.together) {
4916
+ tryToResolveTogetherRow(request, boundaryRow);
4917
+ }
4918
}
4919
}
4920
5671
}
5672
completedSegments.splice(0, i);
5673
5674
+ const row = boundary.row;
5675
+ if (row !== null && row.together && boundary.pendingTasks === 1) {
5676
+ // "together" rows are blocked on their own boundaries.
5677
+ // We have now flushed all the boundary's segments as partials.
5678
+ // We can now unblock it from blocking the row that will eventually
5679
+ // unblock the boundary itself which can issue its complete instruction.
5680
+ // TODO: Ideally the complete instruction would be in a single <script> tag.
5681
+ if (row.pendingTasks === 1) {
5682
+ unblockSuspenseListRow(request, row);
5683
+ } else {
5684
+ row.pendingTasks--;
5685
+ }
5686
+ }
5687
+
5688
return writeHoistablesForBoundary(
5689
destination,
5690
boundary.contentState,