correctly handle errors inside boundary during reset

pull/16171/head
Rich Harris 3 months ago
parent a49a088661
commit 3ad0519444

@ -2,7 +2,7 @@
import { BOUNDARY_EFFECT, EFFECT_TRANSPARENT } from '#client/constants'; import { BOUNDARY_EFFECT, EFFECT_TRANSPARENT } from '#client/constants';
import { component_context, set_component_context } from '../../context.js'; import { component_context, set_component_context } from '../../context.js';
import { invoke_error_boundary } from '../../error-handling.js'; import { handle_error, invoke_error_boundary } from '../../error-handling.js';
import { block, branch, destroy_effect, pause_effect } from '../../reactivity/effects.js'; import { block, branch, destroy_effect, pause_effect } from '../../reactivity/effects.js';
import { import {
active_effect, active_effect,
@ -36,6 +36,8 @@ function with_boundary(boundary, fn) {
try { try {
fn(); fn();
} catch (e) {
handle_error(e);
} finally { } finally {
set_active_effect(previous_effect); set_active_effect(previous_effect);
set_active_reaction(previous_reaction); set_active_reaction(previous_reaction);
@ -74,7 +76,16 @@ export function boundary(node, props, boundary_fn) {
throw error; throw error;
} }
if (boundary_effect) {
destroy_effect(boundary_effect);
} else if (hydrating) {
set_hydrate_node(hydrate_open);
next();
set_hydrate_node(remove_nodes());
}
var did_reset = false; var did_reset = false;
var calling_on_error = false;
var reset = () => { var reset = () => {
if (did_reset) { if (did_reset) {
@ -84,17 +95,16 @@ export function boundary(node, props, boundary_fn) {
did_reset = true; did_reset = true;
if (calling_on_error) {
w.reset_misuse();
throw error;
}
pause_effect(boundary_effect); pause_effect(boundary_effect);
with_boundary(boundary, () => { with_boundary(boundary, () => {
is_creating_fallback = false; is_creating_fallback = false;
try {
boundary_effect = branch(() => boundary_fn(anchor)); boundary_effect = branch(() => boundary_fn(anchor));
} catch (error) {
// If the new subtree immediately throws during mount, warn the dev.
w.reset_misuse();
throw error;
}
}); });
}; };
@ -102,19 +112,13 @@ export function boundary(node, props, boundary_fn) {
try { try {
set_active_reaction(null); set_active_reaction(null);
calling_on_error = true;
onerror?.(error, reset); onerror?.(error, reset);
calling_on_error = false;
} finally { } finally {
set_active_reaction(previous_reaction); set_active_reaction(previous_reaction);
} }
if (boundary_effect) {
destroy_effect(boundary_effect);
} else if (hydrating) {
set_hydrate_node(hydrate_open);
next();
set_hydrate_node(remove_nodes());
}
if (failed) { if (failed) {
// Render the `failed` snippet in a microtask // Render the `failed` snippet in a microtask
queue_micro_task(() => { queue_micro_task(() => {

@ -0,0 +1,28 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
test({ assert, target, warnings }) {
const [toggle] = target.querySelectorAll('button');
flushSync(() => toggle.click());
assert.htmlEqual(
target.innerHTML,
// TODO the synthetic stack shouldn't be part of the message here
`<button>toggle</button><p>yikes! in {expression} in undefined</p><button>reset</button>`
);
const [, reset] = target.querySelectorAll('button');
flushSync(() => reset.click());
assert.htmlEqual(
target.innerHTML,
`<button>toggle</button><p>yikes! in {expression} in undefined</p><button>reset</button>`
);
flushSync(() => toggle.click());
const [, reset2] = target.querySelectorAll('button');
flushSync(() => reset2.click());
assert.htmlEqual(target.innerHTML, `<button>toggle</button><p>hello!</p>`);
}
});

@ -0,0 +1,20 @@
<script>
let must_throw = $state(false);
function throw_error() {
throw new Error('yikes!');
}
</script>
<button onclick={() => must_throw = !must_throw}>toggle</button>
<svelte:boundary>
<p>{must_throw ? throw_error() : 'hello!'}</p>
{#snippet failed(error, reset)}
<p>{error.message}</p>
<button onclick={reset}>reset</button>
{/snippet}
</svelte:boundary>
Loading…
Cancel
Save