diff --git a/documentation/docs/98-reference/.generated/server-errors.md b/documentation/docs/98-reference/.generated/server-errors.md index c3e8b53c31..fb87cec1f4 100644 --- a/documentation/docs/98-reference/.generated/server-errors.md +++ b/documentation/docs/98-reference/.generated/server-errors.md @@ -1,5 +1,13 @@ +### async_in_sync + +``` +Encountered asynchronous work while rendering synchronously. +``` + +You (or the framework you're using) used `render` with an async component. Either use `renderAsync` or wrap the async component in a `svelte:boundary` with a `pending` snippet. + ### lifecycle_function_unavailable ``` diff --git a/packages/svelte/messages/server-errors/async.md b/packages/svelte/messages/server-errors/async.md new file mode 100644 index 0000000000..7389228b29 --- /dev/null +++ b/packages/svelte/messages/server-errors/async.md @@ -0,0 +1,5 @@ +## async_in_sync + +> Encountered asynchronous work while rendering synchronously. + +You (or the framework you're using) used `render` with an async component. Either use `renderAsync` or wrap the async component in a `svelte:boundary` with a `pending` snippet. diff --git a/packages/svelte/src/internal/server/errors.js b/packages/svelte/src/internal/server/errors.js index 458937218f..4bfdd6f0c6 100644 --- a/packages/svelte/src/internal/server/errors.js +++ b/packages/svelte/src/internal/server/errors.js @@ -2,6 +2,18 @@ export * from '../shared/errors.js'; +/** + * Encountered asynchronous work while rendering synchronously. + * @returns {never} + */ +export function async_in_sync() { + const error = new Error(`async_in_sync\nEncountered asynchronous work while rendering synchronously.\nhttps://svelte.dev/e/async_in_sync`); + + error.name = 'Svelte error'; + + throw error; +} + /** * `%name%(...)` is not available on the server * @param {string} name diff --git a/packages/svelte/src/internal/server/payload.js b/packages/svelte/src/internal/server/payload.js index 0d8d08498c..6dcd0ef67a 100644 --- a/packages/svelte/src/internal/server/payload.js +++ b/packages/svelte/src/internal/server/payload.js @@ -1,3 +1,6 @@ +import { pop, push, set_ssr_context, ssr_context } from './context.js'; +import * as e from './errors.js'; + /** @typedef {'head' | 'body'} PayloadType */ /** @typedef {{ [key in PayloadType]: string }} AccumulatedContent */ /** @typedef {{ start: number, end: number, fn: (content: AccumulatedContent) => AccumulatedContent | Promise }} Compaction */ @@ -9,8 +12,6 @@ * @typedef {string | Payload} PayloadItem */ -import { pop, push, set_ssr_context, ssr_context } from './context.js'; - /** * Payloads are basically a tree of `string | Payload`s, where each `Payload` in the tree represents * work that may or may not have completed. A payload can be {@link collect}ed to aggregate the @@ -198,8 +199,9 @@ export class Payload { */ subsume(other) { if (this.global.mode !== other.global.mode) { - // TODO message - this should be impossible though - throw new Error('invariant: a payload cannot switch modes'); + throw new Error( + "invariant: A payload cannot switch modes. If you're seeing this, there's a compiler bug. File an issue!" + ); } this.global.subsume(other.global); @@ -228,7 +230,9 @@ export class Payload { 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'); + throw new Error( + "invariant: Somehow you've encountered asynchronous work while rendering synchronously. If you're seeing this, there's a compiler bug. File an issue!" + ); } else { Payload.#push_accumulated_content(child, transformed_content); } @@ -281,8 +285,7 @@ export class Payload { const result = render(child_payload); if (result instanceof Promise) { if (this.global.mode === 'sync') { - // TODO more-proper error - throw new Error('Encountered an asynchronous component while rendering synchronously'); + e.async_in_sync(); } // just to avoid unhandled promise rejections -- we'll end up throwing in `collect_async` if something fails result.catch(() => {});