Skip to content

Commit 3a1820d

Browse files
committed
WIP await
1 parent 5f01369 commit 3a1820d

File tree

2 files changed

+59
-116
lines changed

2 files changed

+59
-116
lines changed

packages/svelte/src/internal/client/dom/blocks/await.js

Lines changed: 58 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import {
2323
set_dev_stack
2424
} from '../../context.js';
2525
import { flushSync, is_flushing_sync } from '../../reactivity/batch.js';
26+
import { BranchManager } from './branches.js';
27+
import { capture, unset_context } from '../../reactivity/async.js';
2628

2729
const PENDING = 0;
2830
const THEN = 1;
@@ -33,7 +35,7 @@ const CATCH = 2;
3335
/**
3436
* @template V
3537
* @param {TemplateNode} node
36-
* @param {(() => Promise<V>)} get_input
38+
* @param {(() => any)} get_input
3739
* @param {null | ((anchor: Node) => void)} pending_fn
3840
* @param {null | ((anchor: Node, value: Source<V>) => void)} then_fn
3941
* @param {null | ((anchor: Node, error: unknown) => void)} catch_fn
@@ -44,161 +46,102 @@ export function await_block(node, get_input, pending_fn, then_fn, catch_fn) {
4446
hydrate_next();
4547
}
4648

47-
var anchor = node;
4849
var runes = is_runes();
49-
var active_component_context = component_context;
50-
51-
/** @type {any} */
52-
var component_function = DEV ? component_context?.function : null;
53-
var dev_original_stack = DEV ? dev_stack : null;
54-
55-
/** @type {V | Promise<V> | typeof UNINITIALIZED} */
56-
var input = UNINITIALIZED;
57-
58-
/** @type {Effect | null} */
59-
var pending_effect;
60-
61-
/** @type {Effect | null} */
62-
var then_effect;
63-
64-
/** @type {Effect | null} */
65-
var catch_effect;
6650

6751
var input_source = runes
6852
? source(/** @type {V} */ (undefined))
6953
: mutable_source(/** @type {V} */ (undefined), false, false);
7054
var error_source = runes ? source(undefined) : mutable_source(undefined, false, false);
71-
var resolved = false;
72-
/**
73-
* @param {AwaitState} state
74-
* @param {boolean} restore
75-
*/
76-
function update(state, restore) {
77-
resolved = true;
78-
79-
if (restore) {
80-
set_active_effect(effect);
81-
set_active_reaction(effect); // TODO do we need both?
82-
set_component_context(active_component_context);
83-
if (DEV) {
84-
set_dev_current_component_function(component_function);
85-
set_dev_stack(dev_original_stack);
86-
}
87-
}
8855

89-
try {
90-
if (state === PENDING && pending_fn) {
91-
if (pending_effect) resume_effect(pending_effect);
92-
else pending_effect = branch(() => pending_fn(anchor));
93-
}
94-
95-
if (state === THEN && then_fn) {
96-
if (then_effect) resume_effect(then_effect);
97-
else then_effect = branch(() => then_fn(anchor, input_source));
98-
}
99-
100-
if (state === CATCH && catch_fn) {
101-
if (catch_effect) resume_effect(catch_effect);
102-
else catch_effect = branch(() => catch_fn(anchor, error_source));
103-
}
104-
105-
if (state !== PENDING && pending_effect) {
106-
pause_effect(pending_effect, () => (pending_effect = null));
107-
}
108-
109-
if (state !== THEN && then_effect) {
110-
pause_effect(then_effect, () => (then_effect = null));
111-
}
112-
113-
if (state !== CATCH && catch_effect) {
114-
pause_effect(catch_effect, () => (catch_effect = null));
115-
}
116-
} finally {
117-
if (restore) {
118-
if (DEV) {
119-
set_dev_current_component_function(null);
120-
set_dev_stack(null);
121-
}
56+
var branches = new BranchManager(node);
12257

123-
set_component_context(null);
124-
set_active_reaction(null);
125-
set_active_effect(null);
126-
127-
// without this, the DOM does not update until two ticks after the promise
128-
// resolves, which is unexpected behaviour (and somewhat irksome to test)
129-
if (!is_flushing_sync) flushSync();
130-
}
131-
}
132-
}
133-
134-
var effect = block(() => {
135-
if (input === (input = get_input())) return;
58+
block(() => {
59+
var input = get_input();
60+
var destroyed = false;
13661

13762
/** Whether or not there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */
13863
// @ts-ignore coercing `anchor` to a `Comment` causes TypeScript and Prettier to fight
139-
let mismatch = hydrating && is_promise(input) === (anchor.data === HYDRATION_START_ELSE);
64+
let mismatch = hydrating && is_promise(input) === (node.data === HYDRATION_START_ELSE);
14065

14166
if (mismatch) {
14267
// Hydration mismatch: remove everything inside the anchor and start fresh
143-
anchor = skip_nodes();
144-
145-
set_hydrate_node(anchor);
68+
set_hydrate_node(skip_nodes());
14669
set_hydrating(false);
14770
mismatch = true;
14871
}
14972

15073
if (is_promise(input)) {
151-
var promise = input;
74+
var restore = capture();
75+
var resolved = false;
76+
77+
/**
78+
* @param {() => void} fn
79+
*/
80+
const resolve = (fn) => {
81+
if (destroyed) return;
82+
83+
resolved = true;
84+
restore();
85+
86+
if (hydrating) {
87+
// we want to restore everything _except_ this
88+
set_hydrating(false);
89+
}
90+
91+
try {
92+
fn();
93+
} finally {
94+
unset_context();
15295

153-
resolved = false;
96+
// without this, the DOM does not update until two ticks after the promise
97+
// resolves, which is unexpected behaviour (and somewhat irksome to test)
98+
if (!is_flushing_sync) flushSync();
99+
}
100+
};
154101

155-
promise.then(
102+
input.then(
156103
(value) => {
157-
if (promise !== input) return;
158-
// we technically could use `set` here since it's on the next microtick
159-
// but let's use internal_set for consistency and just to be safe
160-
internal_set(input_source, value);
161-
update(THEN, true);
104+
resolve(() => {
105+
internal_set(input_source, value);
106+
branches.ensure(THEN, then_fn && ((target) => then_fn(target, input_source)));
107+
});
162108
},
163109
(error) => {
164-
if (promise !== input) return;
165-
// we technically could use `set` here since it's on the next microtick
166-
// but let's use internal_set for consistency and just to be safe
167-
internal_set(error_source, error);
168-
update(CATCH, true);
169-
if (!catch_fn) {
170-
// Rethrow the error if no catch block exists
171-
throw error_source.v;
172-
}
110+
resolve(() => {
111+
internal_set(error_source, error);
112+
branches.ensure(THEN, catch_fn && ((target) => catch_fn(target, error_source)));
113+
114+
if (!catch_fn) {
115+
// Rethrow the error if no catch block exists
116+
throw error_source.v;
117+
}
118+
});
173119
}
174120
);
175121

176122
if (hydrating) {
177-
if (pending_fn) {
178-
pending_effect = branch(() => pending_fn(anchor));
179-
}
123+
branches.ensure(PENDING, pending_fn);
180124
} else {
181125
// Wait a microtask before checking if we should show the pending state as
182-
// the promise might have resolved by the next microtask.
126+
// the promise might have resolved by then
183127
queue_micro_task(() => {
184-
if (!resolved) update(PENDING, true);
128+
if (!resolved) {
129+
resolve(() => {
130+
branches.ensure(PENDING, pending_fn);
131+
});
132+
}
185133
});
186134
}
187135
} else {
188136
internal_set(input_source, input);
189-
update(THEN, false);
137+
branches.ensure(THEN, then_fn && ((target) => then_fn(target, input_source)));
190138
}
191139

192140
if (mismatch) {
193141
// continue in hydration mode
194142
set_hydrating(true);
195143
}
196144

197-
// Set the input to something else, in order to disable the promise callbacks
198-
return () => (input = UNINITIALIZED);
145+
return () => (destroyed = true);
199146
});
200-
201-
if (hydrating) {
202-
anchor = hydrate_node;
203-
}
204147
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export function flatten(sync, async, fn) {
8080
* some asynchronous work has happened (so that e.g. `await a + b`
8181
* causes `b` to be registered as a dependency).
8282
*/
83-
function capture() {
83+
export function capture() {
8484
var previous_effect = active_effect;
8585
var previous_reaction = active_reaction;
8686
var previous_component_context = component_context;

0 commit comments

Comments
 (0)