clean up payload a bit

pull/16781/head
S. Elliott Johnson 1 month ago
parent d26fb1f89e
commit 4356b649cd

@ -176,7 +176,7 @@ export async function render_async(component, options = {}) {
for (const cleanup of async_on_destroy) cleanup();
async_on_destroy = prev_on_destroy;
let { head, body } = await payload;
let { head, body } = await payload.collect_async();
head += payload.global.head.title.value;
body = BLOCK_OPEN + body + BLOCK_CLOSE; // this inserts a fake boundary so hydration matches

@ -95,14 +95,6 @@ export class Payload {
}
}
/**
* @param {(value: { head: string, body: string }) => void} onfulfilled
*/
async then(onfulfilled) {
const content = await Payload.#collect_content([this], this.type);
return onfulfilled(content);
}
/**
* @param {string | (() => Promise<string>)} content
*/
@ -126,25 +118,11 @@ export class Payload {
compact({ start, end = this.#out.length, fn }) {
const child = new Payload(this.global, this.local, this);
const to_compact = this.#out.splice(start, end - start, child);
const content = Payload.#collect_content(to_compact, this.type);
if (content instanceof Promise) {
const followup = content
.then((content) => fn(content))
.then((transformed_content) =>
Payload.#push_accumulated_content(child, transformed_content)
);
this.promises.followup.push(followup);
if (this.global.mode === 'sync') {
Payload.#compact(fn, child, to_compact, this.type);
} else {
const transformed_content = fn(content);
if (transformed_content instanceof Promise) {
const followup = transformed_content.then((content) =>
Payload.#push_accumulated_content(child, content)
);
this.promises.followup.push(followup);
} else {
Payload.#push_accumulated_content(child, transformed_content);
}
this.promises.followup.push(Payload.#compact_async(fn, child, to_compact, this.type));
}
}
@ -161,16 +139,15 @@ export class Payload {
* @returns {AccumulatedContent}
*/
collect() {
const content = Payload.#collect_content(this.#out, this.type);
if (content instanceof Promise) {
// TODO improve message
// guess you could also end up here if you called `collect` in an async context but... just don't bro
throw new Error(
'invariant: should never reach this, as child throws when it encounters async work in a synchronous context'
);
}
return Payload.#collect_content([this], this.type);
}
return content;
/**
* Collect all of the code from the `out` array and return it as a string.
* @returns {Promise<AccumulatedContent>}
*/
collect_async() {
return Payload.#collect_content_async([this], this.type);
}
copy() {
@ -205,63 +182,52 @@ export class Payload {
return this.#out.length;
}
/**
* @param {(content: AccumulatedContent) => AccumulatedContent | Promise<AccumulatedContent>} fn
* @param {Payload} child
* @param {PayloadItem[]} to_compact
* @param {PayloadType} type
*/
static #compact(fn, child, to_compact, type) {
const content = Payload.#collect_content(to_compact, type);
const transformed_content = fn(content);
if (transformed_content instanceof Promise) {
throw new Error('invariant: should never reach this');
} else {
Payload.#push_accumulated_content(child, transformed_content);
}
}
/**
* @param {(content: AccumulatedContent) => AccumulatedContent | Promise<AccumulatedContent>} fn
* @param {Payload} child
* @param {PayloadItem[]} to_compact
* @param {PayloadType} type
*/
static async #compact_async(fn, child, to_compact, type) {
const content = await Payload.#collect_content_async(to_compact, type);
const transformed_content = await fn(content);
Payload.#push_accumulated_content(child, transformed_content);
}
/**
* Collect all of the code from the `out` array and return it as a string, or a promise resolving to a string.
* @param {PayloadItem[]} items
* @param {PayloadType} current_type
* @param {AccumulatedContent} content
* @returns {MaybePromise<AccumulatedContent>}
* @returns {AccumulatedContent}
*/
static #collect_content(items, current_type, content = { head: '', body: '' }) {
/** @type {MaybePromise<AccumulatedContent>[]} */
const segments = [];
let has_async = false;
const flush = () => {
if (content.head || content.body) {
segments.push(content);
content = { head: '', body: '' };
}
};
for (const item of items) {
if (typeof item === 'string') {
content[current_type] += item;
} else if (item instanceof Payload) {
Payload.#collect_content(item.#out, item.type, content);
} else {
flush();
if (item instanceof Promise) {
has_async = true;
segments.push(
item.then((resolved) => {
const content = { head: '', body: '' };
content[current_type] = resolved;
return content;
})
);
} else if (item.promises.initial || item.promises.followup.length) {
has_async = true;
segments.push(Payload.#collect_content_async([item], current_type));
} else {
const sub = Payload.#collect_content(item.#out, item.type);
if (sub instanceof Promise) {
has_async = true;
}
segments.push(sub);
}
throw new Error('invariant: should never reach this');
}
}
flush();
if (has_async) {
return Promise.all(segments).then((content_array) =>
Payload.#squash_accumulated_content(content_array)
);
}
// No async segments — combine synchronously
return Payload.#squash_accumulated_content(/** @type {AccumulatedContent[]} */ (segments));
return content;
}
/**
@ -304,21 +270,6 @@ export class Payload {
tree.#out.push(child);
}
}
/**
* @param {AccumulatedContent[]} content_array
* @returns {AccumulatedContent}
*/
static #squash_accumulated_content(content_array) {
return content_array.reduce(
(acc, content) => {
acc.head += content.head;
acc.body += content.body;
return acc;
},
{ head: '', body: '' }
);
}
}
export class TreeState {

@ -73,21 +73,7 @@ test('creating an async child in a sync context throws', () => {
).toThrow('Encountered an asynchronous component while rendering synchronously');
});
test('awaiting payload resolves async children', async () => {
const payload = new Payload(new TreeState('async'));
payload.push('a');
payload.child(async ($$payload) => {
await Promise.resolve();
$$payload.push('x');
});
payload.push('y');
const { body, head } = await payload;
assert.equal(head, '');
assert.equal(body, 'axy');
});
test('then() allows awaiting payload to get aggregated content', async () => {
test('collect_async allows awaiting payload to get aggregated content', async () => {
const payload = new Payload(new TreeState('async'));
payload.push('1');
payload.child(async ($$payload) => {
@ -96,7 +82,7 @@ test('then() allows awaiting payload to get aggregated content', async () => {
});
payload.push('3');
const result = await payload;
const result = await payload.collect_async();
assert.deepEqual(result, { head: '', body: '123' });
});
@ -132,7 +118,7 @@ test('compact schedules followup when compaction input is async', async () => {
fn: (content) => ({ body: content.body.toLowerCase(), head: '' })
});
const { body, head } = await payload;
const { body, head } = await payload.collect_async();
assert.equal(head, '');
assert.equal(body, 'axb');
});
@ -261,7 +247,7 @@ test('push accepts async functions in async context', async () => {
});
payload.push('c');
const { head, body } = await payload;
const { head, body } = await payload.collect_async();
assert.equal(head, '');
assert.equal(body, 'abc');
});
@ -284,7 +270,7 @@ test('push handles async functions with different timing', async () => {
// Regular string
payload.push('sync');
const { head, body } = await payload;
const { head, body } = await payload.collect_async();
assert.equal(head, '');
assert.equal(body, 'fastslowsync');
});
@ -296,7 +282,7 @@ test('push async functions work with head content type', async () => {
return '<title>Async Title</title>';
});
const { head, body } = await payload;
const { head, body } = await payload.collect_async();
assert.equal(body, '');
assert.equal(head, '<title>Async Title</title>');
});
@ -316,7 +302,7 @@ test('push async functions can be mixed with child payloads', async () => {
payload.push('-end');
const { head, body } = await payload;
const { head, body } = await payload.collect_async();
assert.equal(head, '');
assert.equal(body, 'start-async-child--end');
});
@ -335,7 +321,7 @@ test('push async functions work with compact operations', async () => {
fn: (content) => ({ head: '', body: content.body.toUpperCase() })
});
const { head, body } = await payload;
const { head, body } = await payload.collect_async();
assert.equal(head, '');
assert.equal(body, 'ABC');
});

Loading…
Cancel
Save