mirror of https://github.com/sveltejs/svelte
fix: `{#await await ...}` and async dependencies fixes (#18243)
This started out as an investigation to get rid of the runtime code for
`{#await ...}` that deactivated a batch prior to reading the promise
function. That can result in a new batch being created if the promise
invocation happens to write a source.
Through that I discovered two other bugs:
1. The way we handle `{#await await ...}` was flawed around
SSR/hydration: On the server it would await the expression (which it
should not; `{#await await ...}` is kind of a weird special case here)
and during hydration it did not produce matching nodes, leading to
hydration fails.
2. When reading a dependency after an await expression which we add to
the current reaction, we did not deduplicate those reads. That can lead
to duplicate dependencies, which in turn can lead to bugs when
`remove_reaction` later runs. Conside this: You have `deps = [count,
unrelated, count]`. Now you do `remove_reactions(deps, 1)`, i.e. "remove
all reactions after the first one". That means the "disconnect these
from each other" logic runs for `count`, too, because it's also in the
third position, but that is wrong because it is also in the first
position, i.e. the connection should be kept.
---------
Co-authored-by: Rich Harris <rich.harris@vercel.com>
pull/18246/head
parent
be8ffeb2a4
commit
000c594e05
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: don't unset batch when calling `{#await ...}` promise
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: promise-ify `{#await await ...}` expressions on the server and correctly hydrate them on the client
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: deduplicate dependencies that are added outside the init/update cycle
|
||||
@ -0,0 +1,24 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
async test({ assert, target }) {
|
||||
await tick();
|
||||
const [incremet, resolve] = target.querySelectorAll('button');
|
||||
|
||||
incremet.click();
|
||||
await tick();
|
||||
incremet.click();
|
||||
await tick();
|
||||
resolve.click();
|
||||
await tick();
|
||||
resolve.click();
|
||||
await tick();
|
||||
resolve.click();
|
||||
await tick();
|
||||
resolve.click();
|
||||
await tick();
|
||||
|
||||
assert.htmlEqual(target.innerHTML, '<button>increment</button> <button>resolve</button> 4 4 1');
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,22 @@
|
||||
<script>
|
||||
let count = $state(0);
|
||||
|
||||
const queue = [];
|
||||
|
||||
function push(v) {
|
||||
if (v === 0) return v;
|
||||
|
||||
return new Promise((fulfil) => queue.push(() => fulfil(v)));
|
||||
}
|
||||
|
||||
async function request(v) {
|
||||
const result = $derived(await push(v));
|
||||
return result + count;
|
||||
}
|
||||
</script>
|
||||
|
||||
<button onclick={() => count++}>increment</button>
|
||||
<button onclick={() => queue.shift()?.()}>resolve</button>
|
||||
{#await request(count) then result}{result}{/await}
|
||||
{#await await push(count) + count then result}{result}{/await}
|
||||
{#await await 1 then result}{result}{/await}
|
||||
@ -0,0 +1,43 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
async test({ assert, target }) {
|
||||
await tick();
|
||||
const [increment, resolve] = target.querySelectorAll('button');
|
||||
|
||||
increment.click();
|
||||
await tick();
|
||||
increment.click();
|
||||
await tick();
|
||||
resolve.click();
|
||||
await tick();
|
||||
resolve.click();
|
||||
await tick();
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`
|
||||
<button>increment</button>
|
||||
<button>resolve</button>
|
||||
4
|
||||
`
|
||||
);
|
||||
|
||||
increment.click();
|
||||
await tick();
|
||||
increment.click();
|
||||
await tick();
|
||||
resolve.click();
|
||||
await tick();
|
||||
resolve.click();
|
||||
await tick();
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`
|
||||
<button>increment</button>
|
||||
<button>resolve</button>
|
||||
8
|
||||
`
|
||||
);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,20 @@
|
||||
<script>
|
||||
let count = $state(0);
|
||||
|
||||
const queue = [];
|
||||
|
||||
function push(v) {
|
||||
if (v === 0) return v;
|
||||
|
||||
return new Promise((fulfil) => queue.push(() => fulfil(v)));
|
||||
}
|
||||
|
||||
async function request(v) {
|
||||
const result = $derived(await push(v));
|
||||
return result + count; // read existing dependency `count` once more
|
||||
}
|
||||
</script>
|
||||
|
||||
<button onclick={() => count++}>increment</button>
|
||||
<button onclick={() => queue.shift()?.()}>resolve</button>
|
||||
{await request(count)}
|
||||
Loading…
Reference in new issue