Skip to content

Commit ab5aeda

Browse files
use helper for async bodies (#16641)
* use helper for async bodies * unused * fix * failing test + fix --------- Co-authored-by: Simon Holthausen <[email protected]>
1 parent af11ee5 commit ab5aeda

File tree

10 files changed

+90
-80
lines changed

10 files changed

+90
-80
lines changed

packages/svelte/src/compiler/phases/3-transform/client/transform-client.js

Lines changed: 20 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -359,16 +359,31 @@ export function client_component(analysis, options) {
359359
if (dev) push_args.push(b.id(analysis.name));
360360

361361
let component_block = b.block([
362+
store_init,
362363
...store_setup,
363364
...legacy_reactive_declarations,
364365
...group_binding_declarations,
365-
...state.instance_level_snippets,
366-
.../** @type {ESTree.Statement[]} */ (instance.body),
367-
analysis.runes || !analysis.needs_context
368-
? b.empty
369-
: b.stmt(b.call('$.init', analysis.immutable ? b.true : undefined))
366+
...state.instance_level_snippets
370367
]);
371368

369+
if (analysis.instance.has_await) {
370+
const body = b.block([
371+
.../** @type {ESTree.Statement[]} */ (instance.body),
372+
b.if(b.call('$.aborted'), b.return()),
373+
.../** @type {ESTree.Statement[]} */ (template.body)
374+
]);
375+
376+
component_block.body.push(b.stmt(b.call(`$.async_body`, b.arrow([], body, true))));
377+
} else {
378+
component_block.body.push(.../** @type {ESTree.Statement[]} */ (instance.body));
379+
380+
if (!analysis.runes && analysis.needs_context) {
381+
component_block.body.push(b.stmt(b.call('$.init', analysis.immutable ? b.true : undefined)));
382+
}
383+
384+
component_block.body.push(.../** @type {ESTree.Statement[]} */ (template.body));
385+
}
386+
372387
if (analysis.needs_mutation_validation) {
373388
component_block.body.unshift(
374389
b.var('$$ownership_validator', b.call('$.create_ownership_validator', b.id('$$props')))
@@ -389,52 +404,6 @@ export function client_component(analysis, options) {
389404
analysis.uses_slots ||
390405
analysis.slot_names.size > 0;
391406

392-
if (analysis.instance.has_await) {
393-
const params = [b.id('$$anchor')];
394-
if (should_inject_props) {
395-
params.push(b.id('$$props'));
396-
}
397-
if (store_setup.length > 0) {
398-
params.push(b.id('$$stores'));
399-
}
400-
const body = b.function_declaration(
401-
b.id('$$body'),
402-
params,
403-
b.block([
404-
b.var('$$unsuspend', b.call('$.suspend')),
405-
b.var('$$active', b.id('$.active_effect')),
406-
b.try_catch(
407-
b.block([
408-
...component_block.body,
409-
b.if(b.call('$.aborted'), b.return()),
410-
.../** @type {ESTree.Statement[]} */ (template.body)
411-
]),
412-
b.block([
413-
b.if(
414-
b.unary('!', b.call('$.aborted', b.id('$$active'))),
415-
b.stmt(b.call('$.invoke_error_boundary', b.id('$$error'), b.id('$$active')))
416-
)
417-
])
418-
),
419-
b.stmt(b.call('$$unsuspend'))
420-
]),
421-
true
422-
);
423-
424-
state.hoisted.push(body);
425-
426-
component_block = b.block([
427-
b.var('fragment', b.call('$.comment')),
428-
b.var('node', b.call('$.first_child', b.id('fragment'))),
429-
store_init,
430-
b.stmt(b.call(body.id, b.id('node'), ...params.slice(1))),
431-
b.stmt(b.call('$.append', b.id('$$anchor'), b.id('fragment')))
432-
]);
433-
} else {
434-
component_block.body.unshift(store_init);
435-
component_block.body.push(.../** @type {ESTree.Statement[]} */ (template.body));
436-
}
437-
438407
// trick esrap into including comments
439408
component_block.loc = instance.loc;
440409

packages/svelte/src/compiler/utils/builders.js

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -659,24 +659,6 @@ export function throw_error(str) {
659659
};
660660
}
661661

662-
/**
663-
* @param {ESTree.BlockStatement} body
664-
* @param {ESTree.BlockStatement} handler
665-
* @returns {ESTree.TryStatement}
666-
*/
667-
export function try_catch(body, handler) {
668-
return {
669-
type: 'TryStatement',
670-
block: body,
671-
handler: {
672-
type: 'CatchClause',
673-
param: id('$$error'),
674-
body: handler
675-
},
676-
finalizer: null
677-
};
678-
}
679-
680662
export {
681663
await_builder as await,
682664
let_builder as let,

packages/svelte/src/internal/client/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ export {
9999
with_script
100100
} from './dom/template.js';
101101
export {
102+
async_body,
102103
for_await_track_reactivity_loss,
103104
save,
104105
track_reactivity_loss

packages/svelte/src/internal/client/reactivity/async.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ import {
1111
set_active_effect,
1212
set_active_reaction
1313
} from '../runtime.js';
14-
import { current_batch } from './batch.js';
14+
import { current_batch, suspend } from './batch.js';
1515
import {
1616
async_derived,
1717
current_async_effect,
1818
derived,
1919
derived_safe_equal,
2020
set_from_async_derived
2121
} from './deriveds.js';
22+
import { aborted } from './effects.js';
2223

2324
/**
2425
*
@@ -170,3 +171,21 @@ export function unset_context() {
170171
set_component_context(null);
171172
if (DEV) set_from_async_derived(null);
172173
}
174+
175+
/**
176+
* @param {() => Promise<void>} fn
177+
*/
178+
export async function async_body(fn) {
179+
const unsuspend = suspend();
180+
const active = /** @type {Effect} */ (active_effect);
181+
182+
try {
183+
await fn();
184+
} catch (error) {
185+
if (!aborted(active)) {
186+
invoke_error_boundary(error, active);
187+
}
188+
} finally {
189+
unsuspend();
190+
}
191+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
import { route } from "./main.svelte";
3+
4+
await new Promise(async (_, reject) => {
5+
await Promise.resolve();
6+
route.current = 'other'
7+
route.reject = reject;
8+
});
9+
</script>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { tick } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
html: `<button>reject</button> <p>pending</p>`,
6+
7+
async test({ assert, target }) {
8+
const [reject] = target.querySelectorAll('button');
9+
10+
await tick();
11+
reject.click();
12+
await tick();
13+
assert.htmlEqual(target.innerHTML, '<button>reject</button> <p>route: other</p>');
14+
}
15+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<script module>
2+
import Child from './Child.svelte';
3+
export let route = $state({ current: 'home' });
4+
</script>
5+
6+
<button onclick={() => route.reject()}>reject</button>
7+
8+
<svelte:boundary>
9+
{#if route.current === 'home'}
10+
<Child />
11+
{:else}
12+
<p>route: {route.current}</p>
13+
{/if}
14+
15+
{#snippet pending()}
16+
<p>pending</p>
17+
{/snippet}
18+
</svelte:boundary>

packages/svelte/tests/runtime-runes/samples/async-top-level-error-nested/Child.svelte

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
import { route } from "./main.svelte";
33
44
await new Promise(async (_, reject) => {
5-
await Promise.resolve();
6-
route.current = 'other'
75
route.reject = reject;
86
});
97
</script>

packages/svelte/tests/runtime-runes/samples/async-top-level-error-nested/_config.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@ export default test({
77
async test({ assert, target }) {
88
const [reject] = target.querySelectorAll('button');
99

10-
await tick();
1110
reject.click();
1211
await tick();
13-
assert.htmlEqual(target.innerHTML, '<button>reject</button> <p>route: other</p>');
12+
assert.htmlEqual(target.innerHTML, '<button>reject</button> <p>failed</p>');
1413
}
1514
});
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
<script module>
22
import Child from './Child.svelte';
3-
export let route = $state({ current: 'home' });
3+
export let route = $state({});
44
</script>
55

66
<button onclick={() => route.reject()}>reject</button>
77

88
<svelte:boundary>
9-
{#if route.current === 'home'}
10-
<Child />
11-
{:else}
12-
<p>route: {route.current}</p>
13-
{/if}
9+
<Child />
1410

1511
{#snippet pending()}
1612
<p>pending</p>
1713
{/snippet}
14+
15+
{#snippet failed()}
16+
<p>failed</p>
17+
{/snippet}
1818
</svelte:boundary>

0 commit comments

Comments
 (0)