Skip to content

Commit 95e52e6

Browse files
committed
[lib] Replace asyncProxy with createAsyncBinding
1 parent eb55e26 commit 95e52e6

File tree

2 files changed

+55
-60
lines changed

2 files changed

+55
-60
lines changed

.changeset/free-adults-talk.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@sjsf/form": major
3+
---
4+
5+
Replace `asyncProxy` with `createAsyncBinding`
Lines changed: 50 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,67 @@
11
import { untrack } from "svelte";
22

3-
export interface AsyncInput<V> {
4-
/**
5-
* @param isDependencyRegistrationOnlyCall - when `true`, indicates that function is called only for dependency registration and result will be ignored
6-
*/
7-
(isDependencyRegistrationOnlyCall: false, signal: AbortSignal): Promise<V>;
8-
/**
9-
* @param signal used here to simplify typing
10-
*/
11-
(isDependencyRegistrationOnlyCall: true, signal: AbortSignal): void;
3+
import { abortPrevious, createAction } from "./action.svelte.js";
4+
5+
export interface AsyncBindingOptions<I, O> {
6+
initialOutput: O;
7+
toOutput: (signal: AbortSignal, input: I) => Promise<O>;
8+
toInput: (signal: AbortSignal, output: O) => Promise<I>;
9+
setInput: (v: I) => void;
10+
getInput: () => I;
11+
isEqual?: (a: I, b: I | undefined) => boolean;
1212
}
1313

14-
export function asyncProxy<V>(
15-
asyncInput: AsyncInput<V>,
16-
asyncOutput: (value: V, signal: AbortSignal) => Promise<void>,
17-
defaultValue: (prev: V | undefined) => V
18-
) {
19-
let inputsInProcess = $state.raw(0);
20-
let outputsInProcess = $state.raw(0);
21-
let proxyValue = $state.raw<V>();
22-
let inputController = new AbortController();
23-
let outputController = new AbortController();
24-
let ignoreProxyUpdate = true;
25-
const derivation = $derived.by(() => {
26-
const proxyVal = proxyValue;
27-
if (ignoreProxyUpdate) {
28-
ignoreProxyUpdate = false;
29-
asyncInput(true, inputController.signal);
30-
return proxyVal as V;
14+
export function createAsyncBinding<I, O>({
15+
initialOutput,
16+
getInput,
17+
setInput,
18+
toInput,
19+
toOutput,
20+
isEqual = Object.is,
21+
}: AsyncBindingOptions<I, O>) {
22+
let lastInputUpdate: I | undefined;
23+
const toInputAction = createAction({
24+
combinator: abortPrevious,
25+
execute: toInput,
26+
onSuccess(result: I) {
27+
lastInputUpdate = result;
28+
setInput(result);
29+
},
30+
});
31+
32+
let output = $state.raw(initialOutput);
33+
const toOutputAction = createAction({
34+
combinator: abortPrevious,
35+
execute: toOutput,
36+
onSuccess(result: O) {
37+
output = result;
38+
},
39+
});
40+
41+
$effect(() => {
42+
const input = getInput();
43+
if (isEqual(input, lastInputUpdate)) {
44+
return;
3145
}
32-
inputController.abort();
33-
inputController = new AbortController();
34-
outputController.abort();
35-
outputController = new AbortController();
3646
untrack(() => {
37-
inputsInProcess++;
47+
toInputAction.abort();
48+
toOutputAction.run(input);
3849
});
39-
asyncInput(false, inputController.signal)
40-
.then((v) => {
41-
ignoreProxyUpdate = true;
42-
proxyValue = v;
43-
})
44-
.finally(() => {
45-
untrack(() => {
46-
inputsInProcess--;
47-
});
48-
});
49-
return defaultValue(proxyVal);
5050
});
51+
5152
return {
52-
get value() {
53-
return derivation;
53+
get current() {
54+
return output;
5455
},
55-
set value(v) {
56-
if (Object.is(proxyValue, v)) {
57-
return;
58-
}
59-
outputController.abort();
60-
outputController = new AbortController();
61-
outputsInProcess++;
62-
asyncOutput(v, outputController.signal)
63-
.then(() => {
64-
ignoreProxyUpdate = true;
65-
})
66-
.finally(() => {
67-
outputsInProcess--;
68-
});
56+
set current(v) {
57+
toOutputAction.abort();
58+
toInputAction.run(v);
6959
},
7060
get inputProcessing() {
71-
return inputsInProcess > 0;
61+
return toInputAction.isProcessed;
7262
},
7363
get outputProcessing() {
74-
return outputsInProcess > 0;
64+
return toOutputAction.isProcessed;
7565
},
7666
};
7767
}

0 commit comments

Comments
 (0)