pull/16091/head
Rich Harris 4 months ago
parent 7d72187fed
commit 34c2900a64

@ -15,37 +15,25 @@ export function reset_is_throwing_error() {
is_throwing_error = false;
}
/** @type {null | unknown} */
let current_error = null;
/**
* @param {unknown} error
* @param {Effect} effect
* @param {Effect | null} [previous_effect]
*/
export function handle_error(error, effect, previous_effect = null) {
if (is_throwing_error) {
if (previous_effect === null) {
is_throwing_error = false;
}
if (should_rethrow_error(effect)) {
throw error;
}
if (error === current_error) {
// TODO is this necessary?
return;
}
if (previous_effect !== null) {
is_throwing_error = true;
}
if (DEV && error instanceof Error) {
adjust_error(error, effect);
}
invoke_error_boundary(error, effect);
if (should_rethrow_error(effect)) {
throw error;
}
}
/**

@ -343,6 +343,13 @@ export function update_reaction(reaction) {
}
return result;
} catch (error) {
var effect = get_effect(reaction);
if (effect) {
handle_error(error, effect);
} else {
throw error;
}
} finally {
new_deps = previous_deps;
skipped_deps = previous_skipped_deps;
@ -357,6 +364,18 @@ export function update_reaction(reaction) {
}
}
/** @param {Reaction} reaction */
function get_effect(reaction) {
/** @type {Reaction | null;} */
var r = reaction;
while (r !== null && (r.f & DERIVED) !== 0) {
r = /** @type {Derived} */ (r).parent;
}
return /** @type {Effect | null} */ (r);
}
/**
* @template V
* @param {Reaction} signal
@ -470,8 +489,6 @@ export function update_effect(effect) {
if (DEV) {
dev_effect_stack.push(effect);
}
} catch (error) {
handle_error(error, effect, previous_effect);
} finally {
is_updating_effect = was_updating_effect;
active_effect = previous_effect;
@ -570,27 +587,23 @@ function flush_queued_effects(effects) {
var effect = effects[i];
if ((effect.f & (DESTROYED | INERT)) === 0) {
try {
if (check_dirtiness(effect)) {
update_effect(effect);
// Effects with no dependencies or teardown do not get added to the effect tree.
// Deferred effects (e.g. `$effect(...)`) _are_ added to the tree because we
// don't know if we need to keep them until they are executed. Doing the check
// here (rather than in `update_effect`) allows us to skip the work for
// immediate effects.
if (effect.deps === null && effect.first === null && effect.nodes_start === null) {
if (effect.teardown === null) {
// remove this effect from the graph
unlink_effect(effect);
} else {
// keep the effect in the graph, but free up some memory
effect.fn = null;
}
if (check_dirtiness(effect)) {
update_effect(effect);
// Effects with no dependencies or teardown do not get added to the effect tree.
// Deferred effects (e.g. `$effect(...)`) _are_ added to the tree because we
// don't know if we need to keep them until they are executed. Doing the check
// here (rather than in `update_effect`) allows us to skip the work for
// immediate effects.
if (effect.deps === null && effect.first === null && effect.nodes_start === null) {
if (effect.teardown === null) {
// remove this effect from the graph
unlink_effect(effect);
} else {
// keep the effect in the graph, but free up some memory
effect.fn = null;
}
}
} catch (error) {
handle_error(error, effect);
}
}
}
@ -649,12 +662,8 @@ function process_effects(root) {
} else if (is_branch) {
effect.f ^= CLEAN;
} else {
try {
if (check_dirtiness(effect)) {
update_effect(effect);
}
} catch (error) {
handle_error(error, effect);
if (check_dirtiness(effect)) {
update_effect(effect);
}
}

@ -5,9 +5,8 @@ export default test({
test({ assert, target }) {
const btn = target.querySelector('button');
btn?.click();
flushSync();
assert.htmlEqual(target.innerHTML, `<button>change</button><p>Error occured</p>`);
assert.throws(() => {
flushSync(() => btn?.click());
}, /kaboom/);
}
});

@ -1,20 +1,18 @@
<script>
let count = $state(0);
const things = $derived.by(() => {
const d = $derived.by(() => {
if (count === 1) {
throw new Error('123')
throw new Error('kaboom')
}
return [1, 2 ,3]
return count
})
</script>
<button onclick={() => count++}>change</button>
<svelte:boundary>
{#each things as thing}
<p>{thing}</p>
{/each}
{d}
{#snippet failed()}
<p>Error occured</p>

@ -1,7 +1,14 @@
<script>
const { things } = $props();
const { count } = $props();
const d = $derived.by(() => {
if (count === 1) {
throw new Error('kaboom')
}
return count
});
$effect(() => {
things
})
d;
});
</script>

@ -8,6 +8,6 @@ export default test({
btn?.click();
flushSync();
assert.htmlEqual(target.innerHTML, `<button>change</button><p>Error occured</p>`);
assert.htmlEqual(target.innerHTML, `<button>change</button><p>Error occurred</p>`);
}
});

@ -2,21 +2,14 @@
import Child from './Child.svelte';
let count = $state(0);
const things = $derived.by(() => {
if (count === 1) {
throw new Error('123')
}
return [1, 2 ,3]
})
</script>
<button onclick={() => count++}>change</button>
<svelte:boundary>
<Child {things} />
<Child {count} />
{#snippet failed()}
<p>Error occured</p>
<p>Error occurred</p>
{/snippet}
</svelte:boundary>

@ -5,11 +5,11 @@ export default test({
test({ assert, target }) {
let btn = target.querySelector('button');
btn?.click();
btn?.click();
assert.throws(() => {
flushSync();
flushSync(() => {
btn?.click();
btn?.click();
});
}, /test\n\n\tin {expression}\n/);
}
});

@ -1,16 +1,18 @@
<script>
let count = $state(0);
let test = $derived.by(() => {
function maybe_throw() {
if (count > 1) {
throw new Error('test');
}
});
return count;
}
</script>
<svelte:boundary onerror={(e) => { throw(e) }}>
<div>Count: {count}</div>
<button onclick={() => count++}>Increment</button>
{count} / {test}
<div>Count: {count}</div>
<button onclick={() => count++}>Increment</button>
{count} / {maybe_throw()}
</svelte:boundary>

Loading…
Cancel
Save