-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
-
48
-
49
-
50
-
51
-
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
-
60
-
61
-
62
-
63
-
64
-
65
-
66
-
67
-
68
-
69
-
70
-
71
-
72
-
73
-
74
-
75
-
76
-
77
-
78
-
79
-
80
-
81
-
82
-
83
-
84
-
85
-
86
-
87
-
88
-
89
-
90
-
91
-
92
-
93
-
94
-
95
-
96
-
97
-
98
-
99
-
100
-
101
-
102
-
103
-
104
-
105
-
106
-
107
-
108
-
109
-
110
-
111
-
112
-
113
-
114
-
115
-
116
-
117
-
118
# Async sample
This page demonstrates an example approach declarative UI to cooperate with async operation naturally.
The example uses a help function named `series`, which leverages Async Generator Function to allow users write multi-step UI smoothly.
Sample application loads specified user info asynchronously then display that as `<dl>`.
## Explicit resource association is required
Signals and Deriveds/Effects are associated using function call stack by design.
This design choice has one big drawback: everything must be a synchronous normal function call.
For example, this code does not work as intended due to the changes to `$name` will not re-run `e1`.
```ts
const $name = signal("Alice");
// 1. global stack = []
const e1 = effect(() => {
// 2. global stack = [e1]
const tid = setTimeout(() => {
// 4. global stack = []
// next loop = outside of `e1`
// When this line is executed, `e1` is popped from global stack.
// Therefore, `$name.get()` would be orphan and changes to `$name` does not
// re-run `e1`.
console.log($name.get());
}, 500);
return () => {
clearTimeout(tid);
};
});
// 3. global stack = []
```
There are two way to tackle this problem.
### Retrieve value early
If you call `.get()` eagerly (when the call stack is not emptied yet), there is nothing to care about.
```ts
// ...
const e1 = effect(() => {
// global stack = [e1]
// Hence `$name` is associated to `e1`
const name = $name.get();
const tid = setTimeout(() => {
console.log(name);
}, 500);
return () => {
clearTimeout(tid);
};
});
```
### Manually specify dependant effect
`effect` function passes itself as a first argument to the callback.
When `Signal.prototype.get()` and `Derived.prototype.get()` saw the first parameter, they treat the first parameter as a dependant instead of the global stack.
By using those API, you can explicitly specify which value re-runs which effect on value changes.
```ts
const e1 = effect((ctx) => {
const tid = setTimeout(() => {
// global stack is empty, but `.get(ctx)` refers the first argument
// instead of global stack so `$name` is associated to `e1`.
console.log($name.get(ctx));
}, 500);
return () => {
clearTimeout(tid);
};
});
```
While this approach is more flexible as it allows values to associate themselves to an effect lazily, this could be more error-prone because it's easy to forget put the context to the first parameter.
I recommend you to carefully evaluate all three approaches before integrating into your app.
The helper function in this example page uses this approach, mainly for demonstrating lazy association.
## Alternative
Use `asyncDerived`. It's the most straightforward way to represent async procedure.
```ts
import { asyncDerived } from "@pocka/ef";
const $myApiResult = asyncDerived(async () => {
const resp = await fetch("/my/api");
if (resp.status !== 200) {
throw new Error(`Unexpected status code (HTTP ${resp.status})`);
}
return resp.json();
});
const myUI = derived(() => {
const myApiResult = $myApiResult.get();
if (!myApiResult.isSettled) {
return el("p", [], ["Fetching"]);
}
if (myApiResult.isRejected) {
return el("p", [], ["ERROR: " + String(myApiResult.error)]);
}
return el("div", [], [doSomethingWithApiData(myApiResult.data)]);
});
```