Skip to content

Commit bc42fa0

Browse files
committed
Merge branch 'async' into aa-coordination-resource
2 parents 73d10e7 + 726b3f1 commit bc42fa0

File tree

4 files changed

+105
-29
lines changed

4 files changed

+105
-29
lines changed

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

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { tracing_mode_flag } from '../../flags/index.js';
3030
import { capture, suspend } from '../dom/blocks/boundary.js';
3131
import { component_context } from '../context.js';
3232
import { noop } from '../../shared/utils.js';
33+
import { UNINITIALIZED } from '../../../constants.js';
3334

3435
/** @type {Effect | null} */
3536
export let from_async_derived = null;
@@ -99,55 +100,65 @@ export function async_derived(fn, detect_waterfall = true) {
99100
}
100101

101102
var promise = /** @type {Promise<V>} */ (/** @type {unknown} */ (undefined));
102-
var value = source(/** @type {V} */ (undefined));
103+
var signal = source(/** @type {V} */ (UNINITIALIZED));
103104

104105
// only suspend in async deriveds created on initialisation
105106
var should_suspend = !active_reaction;
106107

108+
/** @type {(() => void) | null} */
109+
var unsuspend = null;
110+
107111
// TODO this isn't a block
108-
block(async () => {
112+
block(() => {
109113
if (DEV) from_async_derived = active_effect;
110114
var current = (promise = fn());
111115
if (DEV) from_async_derived = null;
112116

113117
var restore = capture();
114-
var unsuspend = should_suspend ? suspend() : noop;
118+
if (should_suspend) unsuspend ??= suspend();
115119

116-
try {
117-
var v = await promise;
120+
promise.then(
121+
(v) => {
122+
if ((parent.f & DESTROYED) !== 0) {
123+
return;
124+
}
118125

119-
if ((parent.f & DESTROYED) !== 0) {
120-
return;
121-
}
126+
if (promise === current) {
127+
restore();
128+
from_async_derived = null;
122129

123-
if (promise === current) {
124-
restore();
125-
from_async_derived = null;
130+
internal_set(signal, v);
126131

127-
internal_set(value, v);
132+
if (DEV && detect_waterfall) {
133+
recent_async_deriveds.add(signal);
128134

129-
if (DEV && detect_waterfall) {
130-
recent_async_deriveds.add(value);
135+
setTimeout(() => {
136+
if (recent_async_deriveds.has(signal)) {
137+
w.await_waterfall();
138+
recent_async_deriveds.delete(signal);
139+
}
140+
});
141+
}
131142

132-
setTimeout(() => {
133-
if (recent_async_deriveds.has(value)) {
134-
w.await_waterfall();
135-
recent_async_deriveds.delete(value);
136-
}
137-
});
143+
// TODO we should probably null out active effect here,
144+
// rather than inside `restore()`
145+
unsuspend?.();
146+
unsuspend = null;
138147
}
148+
},
149+
(e) => {
150+
handle_error(e, parent, null, parent.ctx);
139151
}
140-
} catch (e) {
141-
handle_error(e, parent, null, parent.ctx);
142-
} finally {
143-
unsuspend();
144-
145-
// TODO we should probably null out active effect here,
146-
// rather than inside `restore()`
147-
}
152+
);
148153
}, EFFECT_PRESERVED);
149154

150-
return Promise.resolve(promise).then(() => value);
155+
return new Promise(async (fulfil) => {
156+
// if the effect re-runs before the initial promise
157+
// resolves, delay resolution until we have a value
158+
var p;
159+
while (p !== (p = promise)) await p;
160+
fulfil(signal);
161+
});
151162
}
152163

153164
/**
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
let { promise } = $props();
3+
4+
let d = $derived({
5+
value: await promise
6+
});
7+
</script>
8+
9+
<p>{(await d).value}</p>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { flushSync, tick } from 'svelte';
2+
import { deferred } from '../../../../src/internal/shared/utils.js';
3+
import { test } from '../../test';
4+
5+
/** @type {ReturnType<typeof deferred>} */
6+
let d1;
7+
8+
export default test({
9+
html: `<p>pending</p>`,
10+
11+
get props() {
12+
d1 = deferred();
13+
14+
return {
15+
promise: d1.promise
16+
};
17+
},
18+
19+
async test({ assert, target, component, errors }) {
20+
await Promise.resolve();
21+
var d2 = deferred();
22+
component.promise = d2.promise;
23+
24+
d1.resolve('unused');
25+
await Promise.resolve();
26+
await Promise.resolve();
27+
d2.resolve('hello');
28+
29+
await Promise.resolve();
30+
await Promise.resolve();
31+
await Promise.resolve();
32+
await Promise.resolve();
33+
await Promise.resolve();
34+
await Promise.resolve();
35+
await Promise.resolve();
36+
await Promise.resolve();
37+
await tick();
38+
39+
assert.htmlEqual(target.innerHTML, '<p>hello</p>');
40+
41+
assert.deepEqual(errors, []);
42+
}
43+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script>
2+
import Child from './Child.svelte';
3+
4+
let { promise } = $props();
5+
</script>
6+
7+
<svelte:boundary>
8+
<Child {promise} />
9+
10+
{#snippet pending()}
11+
<p>pending</p>
12+
{/snippet}
13+
</svelte:boundary>

0 commit comments

Comments
 (0)