diff --git a/.changeset/social-taxis-tell.md b/.changeset/social-taxis-tell.md new file mode 100644 index 000000000000..ea23a01def54 --- /dev/null +++ b/.changeset/social-taxis-tell.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure async `@const` in boundary hydrates correctly diff --git a/.changeset/stale-items-know.md b/.changeset/stale-items-know.md new file mode 100644 index 000000000000..60fa85f595b5 --- /dev/null +++ b/.changeset/stale-items-know.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: parallelize async `@const`s in the template diff --git a/packages/svelte/src/compiler/phases/1-parse/utils/create.js b/packages/svelte/src/compiler/phases/1-parse/utils/create.js index 2fba918f20ee..6030f1bd7bff 100644 --- a/packages/svelte/src/compiler/phases/1-parse/utils/create.js +++ b/packages/svelte/src/compiler/phases/1-parse/utils/create.js @@ -10,8 +10,7 @@ export function create_fragment(transparent = false) { nodes: [], metadata: { transparent, - dynamic: false, - has_await: false + dynamic: false } }; } diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitExpression.js index 22a89db76e71..545bc3be2790 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitExpression.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitExpression.js @@ -26,10 +26,6 @@ export function AwaitExpression(node, context) { if (context.state.expression) { context.state.expression.has_await = true; - if (context.state.fragment && context.path.some((node) => node.type === 'ConstTag')) { - context.state.fragment.metadata.has_await = true; - } - suspend = true; } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts b/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts index 410ad120d7e1..d64b1d41265c 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts +++ b/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts @@ -51,6 +51,11 @@ export interface ComponentClientTransformState extends ClientTransformState { readonly after_update: Statement[]; /** Transformed `{@const }` declarations */ readonly consts: Statement[]; + /** Transformed async `{@const }` declarations (if any) and those coming after them */ + async_consts?: { + id: Identifier; + thunks: Expression[]; + }; /** Transformed `let:` directives */ readonly let_directives: Statement[]; /** Memoized expressions */ 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 81dd9e07edd5..f3d7a3549c07 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 @@ -24,15 +24,15 @@ export function ConstTag(node, context) { expression = b.call('$.tag', expression, b.literal(declaration.id.name)); } - context.state.consts.push(b.const(declaration.id, expression)); - context.state.transform[declaration.id.name] = { read: get_value }; - // we need to eagerly evaluate the expression in order to hit any - // 'Cannot access x before initialization' errors - if (dev) { - context.state.consts.push(b.stmt(b.call('$.get', declaration.id))); - } + add_const_declaration( + context.state, + declaration.id, + expression, + node.metadata.expression.has_await, + context.state.scope.get_bindings(declaration) + ); } else { const identifiers = extract_identifiers(declaration.id); const tmp = b.id(context.state.scope.generate('computed_const')); @@ -69,13 +69,13 @@ export function ConstTag(node, context) { expression = b.call('$.tag', expression, b.literal('[@const]')); } - context.state.consts.push(b.const(tmp, expression)); - - // we need to eagerly evaluate the expression in order to hit any - // 'Cannot access x before initialization' errors - if (dev) { - context.state.consts.push(b.stmt(b.call('$.get', tmp))); - } + add_const_declaration( + context.state, + tmp, + expression, + node.metadata.expression.has_await, + context.state.scope.get_bindings(declaration) + ); for (const node of identifiers) { context.state.transform[node.name] = { @@ -84,3 +84,39 @@ export function ConstTag(node, context) { } } } + +/** + * @param {ComponentContext['state']} state + * @param {import('estree').Identifier} id + * @param {import('estree').Expression} expression + * @param {boolean} has_await + * @param {import('#compiler').Binding[]} bindings + */ +function add_const_declaration(state, id, expression, has_await, bindings) { + // we need to eagerly evaluate the expression in order to hit any + // 'Cannot access x before initialization' errors + const after = dev ? [b.stmt(b.call('$.get', id))] : []; + + if (has_await || state.async_consts) { + const run = (state.async_consts ??= { + id: b.id(state.scope.generate('promises')), + thunks: [] + }); + + state.consts.push(b.let(id)); + + const assignment = b.assignment('=', id, expression); + const body = after.length === 0 ? assignment : b.block([b.stmt(assignment), ...after]); + + run.thunks.push(b.thunk(body, has_await)); + + const blocker = b.member(run.id, b.literal(run.thunks.length - 1), true); + + for (const binding of bindings) { + binding.blocker = blocker; + } + } else { + state.consts.push(b.const(id, expression)); + state.consts.push(...after); + } +} diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js index 8d6a2fac8825..ff2436779b92 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js @@ -48,8 +48,6 @@ export function Fragment(node, context) { const is_single_child_not_needing_template = trimmed.length === 1 && (trimmed[0].type === 'SvelteFragment' || trimmed[0].type === 'TitleElement'); - const has_await = context.state.init !== null && (node.metadata.has_await || false); - const template_name = context.state.scope.root.unique('root'); // TODO infer name from parent /** @type {Statement[]} */ @@ -72,7 +70,8 @@ export function Fragment(node, context) { metadata: { namespace, bound_contenteditable: context.state.metadata.bound_contenteditable - } + }, + async_consts: undefined }; for (const node of hoisted) { @@ -153,8 +152,8 @@ export function Fragment(node, context) { body.push(...state.let_directives, ...state.consts); - if (has_await) { - body.push(b.if(b.call('$.aborted'), b.return())); + if (state.async_consts && state.async_consts.thunks.length > 0) { + body.push(b.var(state.async_consts.id, b.call('$.run', b.array(state.async_consts.thunks)))); } if (is_text_first) { @@ -177,13 +176,5 @@ export function Fragment(node, context) { body.push(close); } - if (has_await) { - return b.block([ - b.stmt( - b.call('$.async_body', b.id('$$anchor'), b.arrow([b.id('$$anchor')], b.block(body), true)) - ) - ]); - } else { - return b.block(body); - } + return b.block(body); } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SnippetBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SnippetBlock.js index 895522d47ab2..1af737f05b35 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SnippetBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SnippetBlock.js @@ -14,8 +14,6 @@ export function SnippetBlock(node, context) { // TODO hoist where possible /** @type {(Identifier | AssignmentPattern)[]} */ const args = [b.id('$$anchor')]; - const has_await = node.body.metadata.has_await || false; - /** @type {BlockStatement} */ let body; @@ -78,12 +76,8 @@ export function SnippetBlock(node, context) { // in dev we use a FunctionExpression (not arrow function) so we can use `arguments` let snippet = dev - ? b.call( - '$.wrap_snippet', - b.id(context.state.analysis.name), - b.function(null, args, body, has_await) - ) - : b.arrow(args, body, has_await); + ? b.call('$.wrap_snippet', b.id(context.state.analysis.name), b.function(null, args, body)) + : b.arrow(args, body); const declaration = b.const(node.expression, snippet); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteBoundary.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteBoundary.js index 49c89bc438e0..d64fcda2e8ed 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteBoundary.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteBoundary.js @@ -48,7 +48,11 @@ export function SvelteBoundary(node, context) { if (child.type === 'ConstTag') { has_const = true; if (!context.state.options.experimental.async) { - context.visit(child, { ...context.state, consts: const_tags }); + context.visit(child, { + ...context.state, + consts: const_tags, + scope: context.state.scopes.get(node.fragment) ?? context.state.scope + }); } } } @@ -101,7 +105,13 @@ export function SvelteBoundary(node, context) { nodes.push(child); } - const block = /** @type {BlockStatement} */ (context.visit({ ...node.fragment, nodes })); + const block = /** @type {BlockStatement} */ ( + context.visit( + { ...node.fragment, nodes }, + // Since we're creating a new fragment the reference in scopes can't match, so we gotta attach the right scope manually + { ...context.state, scope: context.state.scopes.get(node.fragment) ?? context.state.scope } + ) + ); if (!context.state.options.experimental.async) { block.body.unshift(...const_tags); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js index c7f843af4822..67982b6150b7 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js @@ -105,7 +105,7 @@ export function process_children(nodes, initial, is_element, context) { is_element && // In case it's wrapped in async the async logic will want to skip sibling nodes up until the end, hence we cannot make this controlled // TODO switch this around and instead optimize for elements with a single block child and not require extra comments (neither for async nor normally) - !(node.body.metadata.has_await || node.metadata.expression.is_async()) + !node.metadata.expression.is_async() ) { node.metadata.is_controlled = true; } else { diff --git a/packages/svelte/src/compiler/phases/3-transform/server/types.d.ts b/packages/svelte/src/compiler/phases/3-transform/server/types.d.ts index adde7480cbd1..e7a72fb8ad41 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/types.d.ts +++ b/packages/svelte/src/compiler/phases/3-transform/server/types.d.ts @@ -1,4 +1,10 @@ -import type { Expression, Statement, ModuleDeclaration, LabeledStatement } from 'estree'; +import type { + Expression, + Statement, + ModuleDeclaration, + LabeledStatement, + Identifier +} from 'estree'; import type { AST, Namespace, ValidatedCompileOptions } from '#compiler'; import type { TransformState } from '../types.js'; import type { ComponentAnalysis } from '../../types.js'; @@ -21,6 +27,11 @@ export interface ComponentServerTransformState extends ServerTransformState { readonly namespace: Namespace; readonly preserve_whitespace: boolean; readonly skip_hydration_boundaries: boolean; + /** Transformed async `{@const }` declarations (if any) and those coming after them */ + async_consts?: { + id: Identifier; + thunks: Expression[]; + }; } export type Context = import('zimmerframe').Context; diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/ConstTag.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/ConstTag.js index a8e4e575cc68..c549d1d00945 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/ConstTag.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/ConstTag.js @@ -2,6 +2,7 @@ /** @import { AST } from '#compiler' */ /** @import { ComponentContext } from '../types.js' */ import * as b from '#compiler/builders'; +import { extract_identifiers } from '../../../../utils/ast.js'; /** * @param {AST.ConstTag} node @@ -11,6 +12,29 @@ export function ConstTag(node, context) { const declaration = node.declaration.declarations[0]; const id = /** @type {Pattern} */ (context.visit(declaration.id)); const init = /** @type {Expression} */ (context.visit(declaration.init)); + const has_await = node.metadata.expression.has_await; - context.state.init.push(b.const(id, init)); + if (has_await || context.state.async_consts) { + const run = (context.state.async_consts ??= { + id: b.id(context.state.scope.generate('promises')), + thunks: [] + }); + + const identifiers = extract_identifiers(declaration.id); + const bindings = context.state.scope.get_bindings(declaration); + + for (const identifier of identifiers) { + context.state.init.push(b.let(identifier.name)); + } + + const assignment = b.assignment('=', id, init); + run.thunks.push(b.thunk(b.block([b.stmt(assignment)]), has_await)); + + const blocker = b.member(run.id, b.literal(run.thunks.length - 1), true); + for (const binding of bindings) { + binding.blocker = blocker; + } + } else { + context.state.init.push(b.const(id, init)); + } } diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/EachBlock.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/EachBlock.js index f53a3903c2b9..3c0a8c167696 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/EachBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/EachBlock.js @@ -34,11 +34,7 @@ export function EachBlock(node, context) { const new_body = /** @type {BlockStatement} */ (context.visit(node.body)).body; - if (node.body) - each.push( - // TODO get rid of fragment.has_await - ...(node.body.metadata.has_await ? [create_async_block(b.block(new_body))] : new_body) - ); + if (node.body) each.push(...new_body); const for_loop = b.for( b.declaration('let', [ @@ -61,7 +57,7 @@ export function EachBlock(node, context) { b.if( b.binary('!==', b.member(array_id, 'length'), b.literal(0)), b.block([open, for_loop]), - node.fallback.metadata.has_await ? create_async_block(fallback) : fallback + fallback ) ); } else { diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/Fragment.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/Fragment.js index a1d25980c438..ef5bd985ae5d 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/Fragment.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/Fragment.js @@ -28,7 +28,8 @@ export function Fragment(node, context) { init: [], template: [], namespace, - skip_hydration_boundaries: is_standalone + skip_hydration_boundaries: is_standalone, + async_consts: undefined }; for (const node of hoisted) { @@ -42,5 +43,11 @@ export function Fragment(node, context) { process_children(trimmed, { ...context, state }); + if (state.async_consts && state.async_consts.thunks.length > 0) { + state.init.push( + b.var(state.async_consts.id, b.call('$$renderer.run', b.array(state.async_consts.thunks))) + ); + } + return b.block([...state.init, ...build_template(state.template)]); } diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/IfBlock.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/IfBlock.js index ca614a93e233..e8418343be9b 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/IfBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/IfBlock.js @@ -25,11 +25,7 @@ export function IfBlock(node, context) { const is_async = node.metadata.expression.is_async(); - const has_await = - node.metadata.expression.has_await || - // TODO get rid of this stuff - node.consequent.metadata.has_await || - node.alternate?.metadata.has_await; + const has_await = node.metadata.expression.has_await; if (is_async || has_await) { statement = create_async_block( diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SnippetBlock.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SnippetBlock.js index 5fc865ec586a..7ae2a8e03793 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SnippetBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SnippetBlock.js @@ -3,7 +3,6 @@ /** @import { ComponentContext } from '../types.js' */ import { dev } from '../../../../state.js'; import * as b from '#compiler/builders'; -import { create_async_block } from './shared/utils.js'; /** * @param {AST.SnippetBlock} node @@ -16,10 +15,6 @@ export function SnippetBlock(node, context) { /** @type {BlockStatement} */ (context.visit(node.body)) ); - if (node.body.metadata.has_await) { - fn.body = b.block([create_async_block(fn.body)]); - } - // @ts-expect-error - TODO remove this hack once $$render_inner for legacy bindings is gone fn.___snippet = true; diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js index 45f1b5aad2b3..8a30e765c230 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js @@ -7,8 +7,7 @@ import { block_open, block_open_else, build_attribute_value, - build_template, - create_async_block + build_template } from './shared/utils.js'; /** @@ -43,14 +42,11 @@ export function SvelteBoundary(node, context) { ); const pending = b.call(callee, b.id('$$renderer')); const block = /** @type {BlockStatement} */ (context.visit(node.fragment)); - const statement = node.fragment.metadata.has_await - ? create_async_block(b.block([block])) - : block; context.state.template.push( b.if( callee, b.block(build_template([block_open_else, b.stmt(pending), block_close])), - b.block(build_template([block_open, statement, block_close])) + b.block(build_template([block_open, block, block_close])) ) ); } else { @@ -70,9 +66,6 @@ export function SvelteBoundary(node, context) { } } else { const block = /** @type {BlockStatement} */ (context.visit(node.fragment)); - const statement = node.fragment.metadata.has_await - ? create_async_block(b.block([block])) - : block; - context.state.template.push(block_open, statement, block_close); + context.state.template.push(block_open, block, block_close); } } diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js index 2e7b4a186c0c..6f2ff38bc1c9 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js @@ -244,12 +244,7 @@ export function build_inline_component(node, expression, context) { params.push(pattern); } - const slot_fn = b.arrow( - params, - node.fragment.metadata.has_await - ? b.block([create_async_block(b.block(block.body))]) - : b.block(block.body) - ); + const slot_fn = b.arrow(params, b.block(block.body)); if (slot_name === 'default' && !has_children_prop) { if ( diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts index 3960c95c8f71..fd664f107c0e 100644 --- a/packages/svelte/src/compiler/types/template.d.ts +++ b/packages/svelte/src/compiler/types/template.d.ts @@ -48,8 +48,6 @@ export namespace AST { * Whether or not we need to traverse into the fragment during mount/hydrate */ dynamic: boolean; - /** @deprecated we should get rid of this in favour of the `$$renderer.run` mechanism */ - has_await: boolean; }; } diff --git a/packages/svelte/tests/runtime-runes/samples/async-const/_config.js b/packages/svelte/tests/runtime-runes/samples/async-const/_config.js index 8aeca875f395..c3e74e886a5e 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-const/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/async-const/_config.js @@ -2,11 +2,12 @@ import { tick } from 'svelte'; import { test } from '../../test'; export default test({ - html: `

Loading...

`, + mode: ['async-server', 'client', 'hydrate'], + ssrHtml: `

Hello, world!

5 01234 5 sync 6 5 0`, async test({ assert, target }) { await tick(); - assert.htmlEqual(target.innerHTML, `

Hello, world!

5 01234`); + assert.htmlEqual(target.innerHTML, `

Hello, world!

5 01234 5 sync 6 5 0`); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/async-const/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-const/main.svelte index 7410ff6a6fd0..b7e00803c55a 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-const/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/async-const/main.svelte @@ -3,17 +3,16 @@ + {@const sync = 'sync'} {@const number = await Promise.resolve(5)} - - {#snippet pending()} -

Loading...

- {/snippet} + {@const after_async = number + 1} + {@const { length, 0: first } = await '01234'} {#snippet greet()} {@const greeting = await `Hello, ${name}!`}

{greeting}

{number} - {#if number > 4} + {#if number > 4 && after_async && greeting} {@const length = await number} {#each { length }, index} {@const i = await index} @@ -23,4 +22,5 @@ {/snippet} {@render greet()} + {number} {sync} {after_async} {length} {first}
diff --git a/packages/svelte/tests/snapshot/samples/async-in-derived/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-in-derived/_expected/client/index.svelte.js index e4df43c6c26b..7d1fe4ec67aa 100644 --- a/packages/svelte/tests/snapshot/samples/async-in-derived/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/async-in-derived/_expected/client/index.svelte.js @@ -25,20 +25,23 @@ export default function Async_in_derived($$anchor, $$props) { { var consequent = ($$anchor) => { - $.async_body($$anchor, async ($$anchor) => { - const yes1 = (await $.save($.async_derived(async () => (await $.save(1))())))(); - const yes2 = (await $.save($.async_derived(async () => foo((await $.save(1))()))))(); + let yes1; + let yes2; + let no1; + let no2; - const no1 = $.derived(() => (async () => { - return await 1; - })()); + var promises = $.run([ + async () => yes1 = (await $.save($.async_derived(async () => (await $.save(1))())))(), + async () => yes2 = (await $.save($.async_derived(async () => foo((await $.save(1))()))))(), - const no2 = $.derived(() => (async () => { + () => no1 = $.derived(() => (async () => { return await 1; - })()); + })()), - if ($.aborted()) return; - }); + () => no2 = $.derived(() => (async () => { + return await 1; + })()) + ]); }; $.if(node, ($$render) => { diff --git a/packages/svelte/tests/snapshot/samples/async-in-derived/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-in-derived/_expected/server/index.svelte.js index bece6402c665..1fd184fa79e4 100644 --- a/packages/svelte/tests/snapshot/samples/async-in-derived/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/async-in-derived/_expected/server/index.svelte.js @@ -18,24 +18,38 @@ export default function Async_in_derived($$renderer, $$props) { } ]); - $$renderer.async_block([], async ($$renderer) => { - if (true) { - $$renderer.push(''); - - const yes1 = (await $.save(1))(); - const yes2 = foo((await $.save(1))()); - - const no1 = (async () => { - return await 1; - })(); - - const no2 = (async () => { - return await 1; - })(); - } else { - $$renderer.push(''); - } - }); + if (true) { + $$renderer.push(''); + + let yes1; + let yes2; + let no1; + let no2; + + var promises = $$renderer.run([ + async () => { + yes1 = (await $.save(1))(); + }, + + async () => { + yes2 = foo((await $.save(1))()); + }, + + () => { + no1 = (async () => { + return await 1; + })(); + }, + + () => { + no2 = (async () => { + return await 1; + })(); + } + ]); + } else { + $$renderer.push(''); + } $$renderer.push(``); });