diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/ConstTag.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/ConstTag.js index a9f922623e..d811a0e639 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/ConstTag.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/ConstTag.js @@ -112,7 +112,9 @@ function add_const_declaration(state, id, expression, metadata, bindings) { const assignment = b.assignment('=', id, expression); const body = after.length === 0 ? assignment : b.block([b.stmt(assignment), ...after]); - if (blockers.length > 0) run.thunks.push(b.thunk(b.call('Promise.all', b.array(blockers)))); + if (blockers.length > 0) { + run.thunks.push(b.thunk(b.call('Promise.all', b.array(blockers)))); + } run.thunks.push(b.thunk(body, has_await)); diff --git a/packages/svelte/src/internal/client/dom/blocks/async.js b/packages/svelte/src/internal/client/dom/blocks/async.js index fc04894772..3cc61b4ceb 100644 --- a/packages/svelte/src/internal/client/dom/blocks/async.js +++ b/packages/svelte/src/internal/client/dom/blocks/async.js @@ -1,4 +1,4 @@ -/** @import { TemplateNode, Value } from '#client' */ +/** @import { Blocker, TemplateNode, Value } from '#client' */ import { flatten, is_promise_settled } from '../../reactivity/async.js'; import { Batch, current_batch } from '../../reactivity/batch.js'; import { get } from '../../runtime.js'; @@ -14,12 +14,12 @@ import { get_boundary } from './boundary.js'; /** * @param {TemplateNode} node - * @param {Array>} blockers + * @param {Blocker[]} blockers * @param {Array<() => Promise>} expressions * @param {(anchor: TemplateNode, ...deriveds: Value[]) => void} fn */ export function async(node, blockers = [], expressions = [], fn) { - if (expressions.length === 0 && blockers.length > 0 && blockers.every(is_promise_settled)) { + if (expressions.length === 0 && blockers.every((b) => b.settled)) { fn(node); return; } diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index c1ab97dc61..6f1fa7391e 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -1,4 +1,4 @@ -/** @import { Effect } from '#client' */ +/** @import { Blocker, Effect } from '#client' */ import { DEV } from 'esm-env'; import { hydrating, set_hydrating } from '../hydration.js'; import { get_descriptors, get_prototype_of } from '../../../shared/utils.js'; @@ -483,7 +483,7 @@ function set_attributes( * @param {(...expressions: any) => Record} fn * @param {Array<() => any>} sync * @param {Array<() => Promise>} async - * @param {Array>} blockers + * @param {Blocker[]} blockers * @param {string} [css_hash] * @param {boolean} [should_remove_defaults] * @param {boolean} [skip_warning] diff --git a/packages/svelte/src/internal/client/reactivity/async.js b/packages/svelte/src/internal/client/reactivity/async.js index e65dabb5e3..0f9ead111e 100644 --- a/packages/svelte/src/internal/client/reactivity/async.js +++ b/packages/svelte/src/internal/client/reactivity/async.js @@ -1,5 +1,4 @@ -/** @import { Effect, TemplateNode, Value } from '#client' */ - +/** @import { Blocker, Effect, Value } from '#client' */ import { DESTROYED, STALE_REACTION } from '#client/constants'; import { DEV } from 'esm-env'; import { @@ -36,7 +35,7 @@ export function is_promise_settled(promise) { } /** - * @param {Array>} blockers + * @param {Blocker[]} blockers * @param {Array<() => any>} sync * @param {Array<() => Promise>} async * @param {(values: Value[]) => any} fn @@ -45,7 +44,7 @@ export function flatten(blockers, sync, async, fn) { const d = is_runes() ? derived : derived_safe_equal; // Filter out already-settled blockers - no need to wait for them - var pending = blockers.filter((b) => !is_promise_settled(b)); + var pending = blockers.filter((b) => !b.settled); if (async.length === 0 && pending.length === 0) { fn(sync.map(d)); @@ -57,7 +56,11 @@ export function flatten(blockers, sync, async, fn) { var restore = capture(); var blocker_promise = - pending.length === 1 ? pending[0] : pending.length > 1 ? Promise.all(pending) : null; + pending.length === 1 + ? pending[0].promise + : pending.length > 1 + ? Promise.all(pending.map((b) => b.promise)) + : null; /** @param {Value[]} values */ function finish(values) { @@ -97,7 +100,7 @@ export function flatten(blockers, sync, async, fn) { } /** - * @param {Array>} blockers + * @param {Blocker[]} blockers * @param {(values: Value[]) => any} fn */ export function run_after_blockers(blockers, fn) { @@ -252,13 +255,14 @@ export function run(thunks) { var promise = Promise.resolve(thunks[0]()).catch(handle_error); + /** @type {Blocker} */ + var blocker = { promise, settled: false }; + var blockers = [blocker]; + promise.finally(() => { - settled_promises.add(promise); + blocker.settled = true; }); - /** @type {Array>} */ - var promises = [promise]; - for (const fn of thunks.slice(1)) { promise = promise .then(() => { @@ -275,16 +279,15 @@ export function run(thunks) { }) .catch(handle_error); - const p = promise; + const blocker = { promise, settled: false }; + blockers.push(blocker); - p.finally(() => { - settled_promises.add(p); + promise.finally(() => { + blocker.settled = true; unset_context(); current_batch?.deactivate(); }); - - promises.push(p); } promise @@ -296,5 +299,12 @@ export function run(thunks) { batch.decrement(blocking); }); - return promises; + return blockers; +} + +/** + * @param {Blocker[]} blockers + */ +export function wait(blockers) { + return Promise.all(blockers.map((b) => b.promise)); } diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index 4cba446afc..5b8dee07b9 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -1,4 +1,4 @@ -/** @import { ComponentContext, ComponentContextLegacy, Derived, Effect, TemplateNode, TransitionManager } from '#client' */ +/** @import { Blocker, ComponentContext, ComponentContextLegacy, Derived, Effect, TemplateNode, TransitionManager } from '#client' */ import { is_dirty, active_effect, @@ -361,7 +361,7 @@ export function render_effect(fn, flags = 0) { * @param {(...expressions: any) => void | (() => void)} fn * @param {Array<() => any>} sync * @param {Array<() => Promise>} async - * @param {Array>} blockers + * @param {Blocker[]} blockers */ export function template_effect(fn, sync = [], async = [], blockers = []) { flatten(blockers, sync, async, (values) => { @@ -374,7 +374,7 @@ export function template_effect(fn, sync = [], async = [], blockers = []) { * @param {(...expressions: any) => void | (() => void)} fn * @param {Array<() => any>} sync * @param {Array<() => Promise>} async - * @param {Array>} blockers + * @param {Blocker[]} blockers */ export function deferred_template_effect(fn, sync = [], async = [], blockers = []) { var batch = /** @type {Batch} */ (current_batch); diff --git a/packages/svelte/src/internal/client/reactivity/types.d.ts b/packages/svelte/src/internal/client/reactivity/types.d.ts index 908576922f..8477917991 100644 --- a/packages/svelte/src/internal/client/reactivity/types.d.ts +++ b/packages/svelte/src/internal/client/reactivity/types.d.ts @@ -103,3 +103,8 @@ export interface Effect extends Reaction { export type Source = Value; export type MaybeSource = T | Source; + +export interface Blocker { + promise: Promise; + settled: boolean; +} diff --git a/packages/svelte/src/internal/client/validate.js b/packages/svelte/src/internal/client/validate.js index 0daecdb480..48a44db304 100644 --- a/packages/svelte/src/internal/client/validate.js +++ b/packages/svelte/src/internal/client/validate.js @@ -1,3 +1,4 @@ +/** @import { Blocker } from '#client' */ import { dev_current_component_function } from './context.js'; import { is_array } from '../shared/utils.js'; import * as e from './errors.js'; @@ -41,7 +42,7 @@ export function validate_each_keys(collection, key_fn) { /** * @param {string} binding - * @param {Array>} blockers + * @param {Blocker[]} blockers * @param {() => Record} get_object * @param {() => string} get_property * @param {number} line