fix: never set derived.v inside fork (#18037)

This started out as me implementing
https://github.com/sveltejs/svelte/pull/17998/changes#r3018047965, but
then I realised that I'd also fixed the bug that #17998 addresses. So I
guess it's an alternative to that PR

---------

Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
pull/18054/head
Rich Harris 4 months ago committed by GitHub
parent 345b8ed69f
commit c93e251654
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: never set derived.v inside fork

@ -419,18 +419,22 @@ export class Batch {
* Associate a change to a given source with the current
* batch, noting its previous and current values
* @param {Value} source
* @param {any} old_value
* @param {any} value
* @param {boolean} [is_derived]
*/
capture(source, old_value, is_derived = false) {
if (old_value !== UNINITIALIZED && !this.previous.has(source)) {
this.previous.set(source, old_value);
capture(source, value, is_derived = false) {
if (source.v !== UNINITIALIZED && !this.previous.has(source)) {
this.previous.set(source, source.v);
}
// Don't save errors in `batch_values`, or they won't be thrown in `runtime.js#get`
if ((source.f & ERROR_VALUE) === 0) {
this.current.set(source, [source.v, is_derived]);
batch_values?.set(source, source.v);
this.current.set(source, [value, is_derived]);
batch_values?.set(source, value);
}
if (!this.is_fork) {
source.v = value;
}
}
@ -1162,11 +1166,6 @@ export function fork(fn) {
flushSync(fn);
// revert state changes
for (var [source, value] of batch.previous) {
source.v = value;
}
return {
commit: async () => {
if (committed) {

@ -384,7 +384,6 @@ export function execute_derived(derived) {
* @returns {void}
*/
export function update_derived(derived) {
var old_value = derived.v;
var value = execute_derived(derived);
if (!derived.equals(value)) {
@ -395,8 +394,11 @@ export function update_derived(derived) {
// otherwise, the next time we get here after a 'real world' state
// change, `derived.equals` may incorrectly return `true`
if (!current_batch?.is_fork || derived.deps === null) {
derived.v = value;
current_batch?.capture(derived, old_value, true);
if (current_batch !== null) {
current_batch.capture(derived, value, true);
} else {
derived.v = value;
}
// deriveds without dependencies should never be recomputed
if (derived.deps === null) {

@ -180,18 +180,10 @@ export function set(source, value, should_proxy = false) {
*/
export function internal_set(source, value, updated_during_traversal = null) {
if (!source.equals(value)) {
var old_value = source.v;
if (is_destroying_effect) {
old_values.set(source, value);
} else {
old_values.set(source, old_value);
}
source.v = value;
old_values.set(source, is_destroying_effect ? value : source.v);
var batch = Batch.ensure();
batch.capture(source, old_value);
batch.capture(source, value);
if (DEV) {
if (tracing_mode_flag || active_effect !== null) {

@ -0,0 +1,22 @@
import { tick } from 'svelte';
import { test } from '../../test';
export default test({
async test({ assert, target }) {
await tick();
const [x, y, resolve, commit] = target.querySelectorAll('button');
const [p] = target.querySelectorAll('p');
y.click();
await tick();
resolve.click();
await tick();
x.click();
await tick();
assert.htmlEqual(p.innerHTML, '1 0');
await tick();
commit.click();
assert.htmlEqual(p.innerHTML, '1 1');
}
});

@ -0,0 +1,21 @@
<script>
import { fork } from 'svelte';
let x = $state(0);
let y = $state(0);
let f;
const deferred = [];
function delay(value) {
if (!value) return value;
return new Promise((resolve) => deferred.push(() => resolve(value)));
}
</script>
<p>{x} {await delay(y)}</p>
<button onclick={() => x += 1}>x</button>
<button onclick={() => f = fork(() => y += 1)}>y (fork)</button>
<button onclick={() => deferred.shift()?.()}>resolve</button>
<button onclick={() => f.commit()}>commit</button>
Loading…
Cancel
Save