Parallel array rendering (#7136)

This commit is contained in:
Johannes Spohr 2023-06-02 10:46:12 +02:00 committed by GitHub
parent 6b8d55b096
commit fea3069360
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 33 additions and 16 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Render arrays of components in parallel

View file

@ -5,6 +5,7 @@ import {
renderAstroTemplateResult,
} from './astro/index.js';
import { SlotString } from './slot.js';
import { bufferIterators } from './util.js';
export async function* renderChild(child: any): AsyncIterable<any> {
child = await child;
@ -16,8 +17,9 @@ export async function* renderChild(child: any): AsyncIterable<any> {
} else if (isHTMLString(child)) {
yield child;
} else if (Array.isArray(child)) {
for (const value of child) {
yield markHTMLString(await renderChild(value));
const bufferedIterators = bufferIterators(child.map((c) => renderChild(c)));
for (const value of bufferedIterators) {
yield markHTMLString(await value);
}
} else if (typeof child === 'function') {
// Special: If a child is a function, call it automatically.

View file

@ -3,7 +3,7 @@ import type { RenderInstruction } from '../types';
import { HTMLBytes, markHTMLString } from '../../escape.js';
import { isPromise } from '../../util.js';
import { renderChild } from '../any.js';
import { EagerAsyncIterableIterator } from '../util.js';
import { bufferIterators } from '../util.js';
const renderTemplateResultSym = Symbol.for('astro.renderTemplateResult');
@ -36,23 +36,15 @@ export class RenderTemplateResult {
async *[Symbol.asyncIterator]() {
const { htmlParts, expressions } = this;
let iterables: Array<EagerAsyncIterableIterator> = [];
// all async iterators start running in non-buffered mode to avoid useless caching
for (let i = 0; i < htmlParts.length; i++) {
iterables.push(new EagerAsyncIterableIterator(renderChild(expressions[i])));
}
// once the execution of the next for loop is suspended due to an async component,
// this timeout triggers and we start buffering the other iterators
setTimeout(() => {
// buffer all iterators that haven't started yet
iterables.forEach((it) => !it.isStarted() && it.buffer());
}, 0);
let iterables = bufferIterators(expressions.map((e) => renderChild(e)));
for (let i = 0; i < htmlParts.length; i++) {
const html = htmlParts[i];
const iterable = iterables[i];
yield markHTMLString(html);
yield* iterable;
if (iterable) {
yield* iterable;
}
}
}
}

View file

@ -139,6 +139,23 @@ export function renderElement(
return `<${name}${internalSpreadAttributes(props, shouldEscape)}>${children}</${name}>`;
}
/**
* This will take an array of async iterables and start buffering them eagerly.
* To avoid useless buffering, it will only start buffering the next tick, so the
* first sync iterables won't be buffered.
*/
export function bufferIterators<T>(iterators: AsyncIterable<T>[]): AsyncIterable<T>[] {
// all async iterators start running in non-buffered mode to avoid useless caching
const eagerIterators = iterators.map((it) => new EagerAsyncIterableIterator(it));
// once the execution of the next for loop is suspended due to an async component,
// this timeout triggers and we start buffering the other iterators
setTimeout(() => {
// buffer all iterators that haven't started yet
eagerIterators.forEach((it) => !it.isStarted() && it.buffer());
}, 0);
return eagerIterators;
}
// This wrapper around an AsyncIterable can eagerly consume its values, so that
// its values are ready to yield out ASAP. This is used for list-like usage of
// Astro components, so that we don't have to wait on earlier components to run

View file

@ -1,8 +1,9 @@
---
import Delayed from '../components/Delayed.astro'
import Delayed from '../components/Delayed.astro';
---
<Delayed ms={30} />
<Delayed ms={20} />
<Delayed ms={40} />
<Delayed ms={10} />
{[<Delayed ms={30} />, <Delayed ms={20} />, <Delayed ms={40} />, <Delayed ms={10} />]}