@zeix/cause-effect 0.13.2 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +160 -130
- package/index.d.ts +7 -5
- package/index.js +1 -1
- package/index.ts +6 -6
- package/package.json +1 -1
- package/src/computed.d.ts +15 -17
- package/src/computed.ts +26 -200
- package/src/effect.d.ts +9 -12
- package/src/effect.ts +54 -28
- package/src/memo.d.ts +13 -0
- package/src/memo.ts +91 -0
- package/src/scheduler.d.ts +15 -11
- package/src/scheduler.ts +32 -15
- package/src/signal.d.ts +6 -20
- package/src/signal.ts +34 -67
- package/src/state.d.ts +4 -7
- package/src/state.ts +9 -39
- package/src/task.d.ts +17 -0
- package/src/task.ts +153 -0
- package/src/util.ts +2 -0
- package/test/batch.test.ts +10 -14
- package/test/benchmark.test.ts +81 -69
- package/test/computed.test.ts +108 -123
- package/test/effect.test.ts +24 -16
- package/test/state.test.ts +2 -53
- package/test/util/dependency-graph.ts +2 -2
package/src/signal.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
type MaybeSignal<T extends {}> =
|
|
1
|
+
import { type ComputedCallback } from './computed';
|
|
2
|
+
type Signal<T extends {}> = {
|
|
3
|
+
get(): T;
|
|
4
|
+
};
|
|
5
|
+
type MaybeSignal<T extends {}> = T | Signal<T> | ComputedCallback<T>;
|
|
6
6
|
declare const UNSET: any;
|
|
7
7
|
/**
|
|
8
8
|
* Check whether a value is a Signal or not
|
|
@@ -28,18 +28,4 @@ declare const isComputedCallback: <T extends {}>(value: unknown) => value is Com
|
|
|
28
28
|
* @returns {Signal<T>} - converted Signal
|
|
29
29
|
*/
|
|
30
30
|
declare const toSignal: <T extends {}>(value: MaybeSignal<T>) => Signal<T>;
|
|
31
|
-
|
|
32
|
-
* Resolve signals or functions using signals and apply callbacks based on the results
|
|
33
|
-
*
|
|
34
|
-
* @since 0.13.0
|
|
35
|
-
* @param {SignalMatcher<S, R>} matcher - SignalMatcher to match
|
|
36
|
-
* @returns {R | Promise<R>} - result of the matched callback
|
|
37
|
-
*/
|
|
38
|
-
declare const match: <S extends Signal<{}>[], R>(matcher: {
|
|
39
|
-
signals: S;
|
|
40
|
-
abort?: AbortSignal;
|
|
41
|
-
ok: ((...values: { [K in keyof S]: S[K] extends Signal<infer T> ? T : never; }) => R | Promise<R>);
|
|
42
|
-
err: ((...errors: Error[]) => R | Promise<R>);
|
|
43
|
-
nil: (abort?: AbortSignal) => R | Promise<R>;
|
|
44
|
-
}) => R | Promise<R>;
|
|
45
|
-
export { type Signal, type MaybeSignal, type ComputedCallback, UNSET, isSignal, isComputedCallback, toSignal, match, };
|
|
31
|
+
export { type Signal, type MaybeSignal, UNSET, isSignal, isComputedCallback, toSignal, };
|
package/src/signal.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { isState, state } from './state'
|
|
2
|
+
import {
|
|
3
|
+
type ComputedCallback,
|
|
4
|
+
isComputed,
|
|
5
|
+
computed,
|
|
6
|
+
} from './computed'
|
|
7
|
+
import { isFunction } from './util'
|
|
4
8
|
|
|
5
9
|
/* === Types === */
|
|
6
10
|
|
|
7
|
-
type Signal<T extends {}> =
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
type Signal<T extends {}> = {
|
|
12
|
+
get(): T
|
|
13
|
+
}
|
|
14
|
+
type MaybeSignal<T extends {}> = T | Signal<T> | ComputedCallback<T>
|
|
10
15
|
|
|
11
16
|
/* === Constants === */
|
|
12
17
|
|
|
@@ -16,87 +21,49 @@ const UNSET: any = Symbol()
|
|
|
16
21
|
|
|
17
22
|
/**
|
|
18
23
|
* Check whether a value is a Signal or not
|
|
19
|
-
*
|
|
24
|
+
*
|
|
20
25
|
* @since 0.9.0
|
|
21
26
|
* @param {unknown} value - value to check
|
|
22
27
|
* @returns {boolean} - true if value is a Signal, false otherwise
|
|
23
28
|
*/
|
|
24
|
-
const isSignal = /*#__PURE__*/ <T extends {}>(
|
|
25
|
-
|
|
29
|
+
const isSignal = /*#__PURE__*/ <T extends {}>(
|
|
30
|
+
value: unknown,
|
|
31
|
+
): value is Signal<T> => isState(value) || isComputed(value)
|
|
26
32
|
|
|
27
33
|
/**
|
|
28
34
|
* Check if the provided value is a callback that may be used as input for toSignal() to derive a computed state
|
|
29
|
-
*
|
|
35
|
+
*
|
|
30
36
|
* @since 0.12.0
|
|
31
37
|
* @param {unknown} value - value to check
|
|
32
38
|
* @returns {boolean} - true if value is a callback or callbacks object, false otherwise
|
|
33
39
|
*/
|
|
34
40
|
const isComputedCallback = /*#__PURE__*/ <T extends {}>(
|
|
35
|
-
value: unknown
|
|
36
|
-
): value is ComputedCallback<T> =>
|
|
37
|
-
isFunction(value) && value.length < 2
|
|
41
|
+
value: unknown,
|
|
42
|
+
): value is ComputedCallback<T> => isFunction(value) && value.length < 2
|
|
38
43
|
|
|
39
44
|
/**
|
|
40
45
|
* Convert a value to a Signal if it's not already a Signal
|
|
41
|
-
*
|
|
46
|
+
*
|
|
42
47
|
* @since 0.9.6
|
|
43
48
|
* @param {MaybeSignal<T>} value - value to convert to a Signal
|
|
44
49
|
* @returns {Signal<T>} - converted Signal
|
|
45
50
|
*/
|
|
46
51
|
const toSignal = /*#__PURE__*/ <T extends {}>(
|
|
47
|
-
value: MaybeSignal<T
|
|
48
|
-
): Signal<T> =>
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
value: MaybeSignal<T>,
|
|
53
|
+
): Signal<T> =>
|
|
54
|
+
isSignal<T>(value)
|
|
55
|
+
? value
|
|
56
|
+
: isComputedCallback<T>(value)
|
|
57
|
+
? computed(value)
|
|
58
|
+
: state(value as T)
|
|
52
59
|
|
|
53
|
-
|
|
54
|
-
* Resolve signals or functions using signals and apply callbacks based on the results
|
|
55
|
-
*
|
|
56
|
-
* @since 0.13.0
|
|
57
|
-
* @param {SignalMatcher<S, R>} matcher - SignalMatcher to match
|
|
58
|
-
* @returns {R | Promise<R>} - result of the matched callback
|
|
59
|
-
*/
|
|
60
|
-
const match = <S extends Signal<{}>[], R>(
|
|
61
|
-
matcher: {
|
|
62
|
-
signals: S,
|
|
63
|
-
abort?: AbortSignal,
|
|
64
|
-
ok: ((...values: {
|
|
65
|
-
[K in keyof S]: S[K] extends Signal<infer T> ? T : never
|
|
66
|
-
}) => R | Promise<R>),
|
|
67
|
-
err: ((...errors: Error[]) => R | Promise<R>),
|
|
68
|
-
nil: (abort?: AbortSignal) => R | Promise<R>
|
|
69
|
-
}
|
|
70
|
-
): R | Promise<R> => {
|
|
71
|
-
const { signals, abort, ok, err, nil } = matcher
|
|
72
|
-
|
|
73
|
-
const errors: Error[] = []
|
|
74
|
-
let suspense = false
|
|
75
|
-
const values = signals.map(signal => {
|
|
76
|
-
try {
|
|
77
|
-
const value = signal.get()
|
|
78
|
-
if (value === UNSET) suspense = true
|
|
79
|
-
return value
|
|
80
|
-
} catch (e) {
|
|
81
|
-
if (isAbortError(e)) throw e
|
|
82
|
-
errors.push(toError(e))
|
|
83
|
-
}
|
|
84
|
-
}) as {
|
|
85
|
-
[K in keyof S]: S[K] extends Signal<infer T extends {}> ? T : never
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
return suspense ? nil(abort)
|
|
90
|
-
: errors.length ? err(...errors)
|
|
91
|
-
: ok(...values)
|
|
92
|
-
} catch (e) {
|
|
93
|
-
if (isAbortError(e)) throw e
|
|
94
|
-
const error = toError(e)
|
|
95
|
-
return err(error)
|
|
96
|
-
}
|
|
97
|
-
}
|
|
60
|
+
/* === Exports === */
|
|
98
61
|
|
|
99
62
|
export {
|
|
100
|
-
type Signal,
|
|
101
|
-
|
|
102
|
-
|
|
63
|
+
type Signal,
|
|
64
|
+
type MaybeSignal,
|
|
65
|
+
UNSET,
|
|
66
|
+
isSignal,
|
|
67
|
+
isComputedCallback,
|
|
68
|
+
toSignal,
|
|
69
|
+
}
|
package/src/state.d.ts
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
import { type TapMatcher } from './effect';
|
|
3
|
-
export type State<T extends {}> = {
|
|
1
|
+
type State<T extends {}> = {
|
|
4
2
|
[Symbol.toStringTag]: 'State';
|
|
5
3
|
get(): T;
|
|
6
4
|
set(v: T): void;
|
|
7
5
|
update(fn: (v: T) => T): void;
|
|
8
|
-
map<U extends {}>(fn: (v: T) => U | Promise<U>): Computed<U>;
|
|
9
|
-
tap(matcher: TapMatcher<T> | ((v: T) => void | (() => void))): () => void;
|
|
10
6
|
};
|
|
11
7
|
/**
|
|
12
8
|
* Create a new state signal
|
|
@@ -15,7 +11,7 @@ export type State<T extends {}> = {
|
|
|
15
11
|
* @param {T} initialValue - initial value of the state
|
|
16
12
|
* @returns {State<T>} - new state signal
|
|
17
13
|
*/
|
|
18
|
-
|
|
14
|
+
declare const state: <T extends {}>(initialValue: T) => State<T>;
|
|
19
15
|
/**
|
|
20
16
|
* Check if the provided value is a State instance
|
|
21
17
|
*
|
|
@@ -23,4 +19,5 @@ export declare const state: <T extends {}>(initialValue: T) => State<T>;
|
|
|
23
19
|
* @param {unknown} value - value to check
|
|
24
20
|
* @returns {boolean} - true if the value is a State instance, false otherwise
|
|
25
21
|
*/
|
|
26
|
-
|
|
22
|
+
declare const isState: <T extends {}>(value: unknown) => value is State<T>;
|
|
23
|
+
export { type State, state, isState };
|
package/src/state.ts
CHANGED
|
@@ -1,25 +1,21 @@
|
|
|
1
1
|
import { UNSET } from './signal'
|
|
2
|
-
import {
|
|
3
|
-
import { isFunction, isObjectOfType } from './util'
|
|
2
|
+
import { isObjectOfType } from './util'
|
|
4
3
|
import { type Watcher, notify, subscribe } from './scheduler'
|
|
5
|
-
import { type TapMatcher, type EffectMatcher, effect } from './effect'
|
|
6
4
|
|
|
7
5
|
/* === Types === */
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
type State<T extends {}> = {
|
|
10
8
|
[Symbol.toStringTag]: 'State'
|
|
11
9
|
get(): T
|
|
12
10
|
set(v: T): void
|
|
13
11
|
update(fn: (v: T) => T): void
|
|
14
|
-
map<U extends {}>(fn: (v: T) => U | Promise<U>): Computed<U>
|
|
15
|
-
tap(matcher: TapMatcher<T> | ((v: T) => void | (() => void))): () => void
|
|
16
12
|
}
|
|
17
13
|
|
|
18
14
|
/* === Constants === */
|
|
19
15
|
|
|
20
16
|
const TYPE_STATE = 'State'
|
|
21
17
|
|
|
22
|
-
/* ===
|
|
18
|
+
/* === Functions === */
|
|
23
19
|
|
|
24
20
|
/**
|
|
25
21
|
* Create a new state signal
|
|
@@ -28,9 +24,7 @@ const TYPE_STATE = 'State'
|
|
|
28
24
|
* @param {T} initialValue - initial value of the state
|
|
29
25
|
* @returns {State<T>} - new state signal
|
|
30
26
|
*/
|
|
31
|
-
|
|
32
|
-
initialValue: T,
|
|
33
|
-
): State<T> => {
|
|
27
|
+
const state = /*#__PURE__*/ <T extends {}>(initialValue: T): State<T> => {
|
|
34
28
|
const watchers: Set<Watcher> = new Set()
|
|
35
29
|
let value: T = initialValue
|
|
36
30
|
|
|
@@ -74,34 +68,6 @@ export const state = /*#__PURE__*/ <T extends {}>(
|
|
|
74
68
|
update: (fn: (v: T) => T): void => {
|
|
75
69
|
s.set(fn(value))
|
|
76
70
|
},
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Create a computed signal from the current state signal
|
|
80
|
-
*
|
|
81
|
-
* @since 0.9.0
|
|
82
|
-
* @param {(v: T) => U | Promise<U>} fn - computed callback
|
|
83
|
-
* @returns {Computed<U>} - computed signal
|
|
84
|
-
*/
|
|
85
|
-
map: <U extends {}>(fn: (v: T) => U | Promise<U>): Computed<U> =>
|
|
86
|
-
computed({
|
|
87
|
-
signals: [s],
|
|
88
|
-
ok: fn,
|
|
89
|
-
}),
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Case matching for the state signal with effect callbacks
|
|
93
|
-
*
|
|
94
|
-
* @since 0.13.0
|
|
95
|
-
* @param {TapMatcher<T> | ((v: T) => void | (() => void))} matcher - tap matcher or effect callback
|
|
96
|
-
* @returns {() => void} - cleanup function for the effect
|
|
97
|
-
*/
|
|
98
|
-
tap: (
|
|
99
|
-
matcher: TapMatcher<T> | ((v: T) => void | (() => void)),
|
|
100
|
-
): (() => void) =>
|
|
101
|
-
effect({
|
|
102
|
-
signals: [s],
|
|
103
|
-
...(isFunction(matcher) ? { ok: matcher } : matcher),
|
|
104
|
-
} as EffectMatcher<[State<T>]>),
|
|
105
71
|
}
|
|
106
72
|
|
|
107
73
|
return s
|
|
@@ -114,6 +80,10 @@ export const state = /*#__PURE__*/ <T extends {}>(
|
|
|
114
80
|
* @param {unknown} value - value to check
|
|
115
81
|
* @returns {boolean} - true if the value is a State instance, false otherwise
|
|
116
82
|
*/
|
|
117
|
-
|
|
83
|
+
const isState = /*#__PURE__*/ <T extends {}>(
|
|
118
84
|
value: unknown,
|
|
119
85
|
): value is State<T> => isObjectOfType(value, TYPE_STATE)
|
|
86
|
+
|
|
87
|
+
/* === Exports === */
|
|
88
|
+
|
|
89
|
+
export { type State, state, isState }
|
package/src/task.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type Computed } from './computed';
|
|
2
|
+
/**
|
|
3
|
+
* Callback for async computation tasks
|
|
4
|
+
* This explicitly returns a Promise<T> to differentiate from MemoCallback
|
|
5
|
+
*
|
|
6
|
+
* @since 0.14.0
|
|
7
|
+
*/
|
|
8
|
+
type TaskCallback<T extends {}> = (abort: AbortSignal) => Promise<T>;
|
|
9
|
+
/**
|
|
10
|
+
* Create a derived signal that supports asynchronous computations
|
|
11
|
+
*
|
|
12
|
+
* @since 0.14.0
|
|
13
|
+
* @param {TaskCallback<T>} fn - async computation callback
|
|
14
|
+
* @returns {Computed<T>} - Computed signal
|
|
15
|
+
*/
|
|
16
|
+
declare const task: <T extends {}>(fn: TaskCallback<T>) => Computed<T>;
|
|
17
|
+
export { type TaskCallback, task };
|
package/src/task.ts
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { UNSET } from './signal'
|
|
2
|
+
import {
|
|
3
|
+
CircularDependencyError,
|
|
4
|
+
isAbortError,
|
|
5
|
+
isPromise,
|
|
6
|
+
toError,
|
|
7
|
+
} from './util'
|
|
8
|
+
import {
|
|
9
|
+
type Cleanup,
|
|
10
|
+
type Watcher,
|
|
11
|
+
flush,
|
|
12
|
+
notify,
|
|
13
|
+
subscribe,
|
|
14
|
+
watch,
|
|
15
|
+
} from './scheduler'
|
|
16
|
+
import {
|
|
17
|
+
type Computed,
|
|
18
|
+
TYPE_COMPUTED,
|
|
19
|
+
} from './computed'
|
|
20
|
+
|
|
21
|
+
/* === Types === */
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Callback for async computation tasks
|
|
25
|
+
* This explicitly returns a Promise<T> to differentiate from MemoCallback
|
|
26
|
+
*
|
|
27
|
+
* @since 0.14.0
|
|
28
|
+
*/
|
|
29
|
+
type TaskCallback<T extends {}> = (abort: AbortSignal) => Promise<T>
|
|
30
|
+
|
|
31
|
+
/* === Function === */
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create a derived signal that supports asynchronous computations
|
|
35
|
+
*
|
|
36
|
+
* @since 0.14.0
|
|
37
|
+
* @param {TaskCallback<T>} fn - async computation callback
|
|
38
|
+
* @returns {Computed<T>} - Computed signal
|
|
39
|
+
*/
|
|
40
|
+
const task = <T extends {}>(fn: TaskCallback<T>): Computed<T> => {
|
|
41
|
+
const watchers: Set<Watcher> = new Set()
|
|
42
|
+
|
|
43
|
+
// Internal state
|
|
44
|
+
let value: T = UNSET
|
|
45
|
+
let error: Error | undefined
|
|
46
|
+
let dirty = true
|
|
47
|
+
let changed = false
|
|
48
|
+
let computing = false
|
|
49
|
+
let controller: AbortController | undefined
|
|
50
|
+
|
|
51
|
+
// Functions to update internal state
|
|
52
|
+
const ok = (v: T) => {
|
|
53
|
+
if (!Object.is(v, value)) {
|
|
54
|
+
value = v
|
|
55
|
+
dirty = false
|
|
56
|
+
error = undefined
|
|
57
|
+
changed = true
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const nil = () => {
|
|
61
|
+
changed = UNSET !== value
|
|
62
|
+
value = UNSET
|
|
63
|
+
error = undefined
|
|
64
|
+
}
|
|
65
|
+
const err = (e: unknown) => {
|
|
66
|
+
const newError = toError(e)
|
|
67
|
+
changed = !(
|
|
68
|
+
error &&
|
|
69
|
+
newError.name === error.name &&
|
|
70
|
+
newError.message === error.message
|
|
71
|
+
)
|
|
72
|
+
value = UNSET
|
|
73
|
+
error = newError
|
|
74
|
+
}
|
|
75
|
+
const resolve = (v: T) => {
|
|
76
|
+
computing = false
|
|
77
|
+
controller = undefined
|
|
78
|
+
ok(v)
|
|
79
|
+
if (changed) notify(watchers)
|
|
80
|
+
}
|
|
81
|
+
const reject = (e: unknown) => {
|
|
82
|
+
computing = false
|
|
83
|
+
controller = undefined
|
|
84
|
+
err(e)
|
|
85
|
+
if (changed) notify(watchers)
|
|
86
|
+
}
|
|
87
|
+
const abort = () => {
|
|
88
|
+
computing = false
|
|
89
|
+
controller = undefined
|
|
90
|
+
compute() // retry
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Called when notified from sources (push)
|
|
94
|
+
const mark = (() => {
|
|
95
|
+
dirty = true
|
|
96
|
+
controller?.abort('Aborted because source signal changed')
|
|
97
|
+
if (watchers.size) {
|
|
98
|
+
notify(watchers)
|
|
99
|
+
} else {
|
|
100
|
+
mark.cleanups.forEach(fn => fn())
|
|
101
|
+
mark.cleanups.clear()
|
|
102
|
+
}
|
|
103
|
+
}) as Watcher
|
|
104
|
+
mark.cleanups = new Set<Cleanup>()
|
|
105
|
+
|
|
106
|
+
// Called when requested by dependencies (pull)
|
|
107
|
+
const compute = () =>
|
|
108
|
+
watch(() => {
|
|
109
|
+
if (computing) throw new CircularDependencyError('task')
|
|
110
|
+
changed = false
|
|
111
|
+
controller = new AbortController()
|
|
112
|
+
controller.signal.addEventListener('abort', abort, {
|
|
113
|
+
once: true,
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
let result: T | Promise<T>
|
|
117
|
+
computing = true
|
|
118
|
+
try {
|
|
119
|
+
result = fn(controller.signal)
|
|
120
|
+
} catch (e) {
|
|
121
|
+
if (isAbortError(e)) nil()
|
|
122
|
+
else err(e)
|
|
123
|
+
computing = false
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
if (isPromise(result)) result.then(resolve, reject)
|
|
127
|
+
else if (null == result || UNSET === result) nil()
|
|
128
|
+
else ok(result)
|
|
129
|
+
computing = false
|
|
130
|
+
}, mark)
|
|
131
|
+
|
|
132
|
+
const c: Computed<T> = {
|
|
133
|
+
[Symbol.toStringTag]: TYPE_COMPUTED,
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get the current value of the computed
|
|
137
|
+
*
|
|
138
|
+
* @returns {T} - current value of the computed
|
|
139
|
+
*/
|
|
140
|
+
get: (): T => {
|
|
141
|
+
subscribe(watchers)
|
|
142
|
+
flush()
|
|
143
|
+
if (dirty) compute()
|
|
144
|
+
if (error) throw error
|
|
145
|
+
return value
|
|
146
|
+
},
|
|
147
|
+
}
|
|
148
|
+
return c
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* === Exports === */
|
|
152
|
+
|
|
153
|
+
export { type TaskCallback, task }
|
package/src/util.ts
CHANGED
package/test/batch.test.ts
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import { describe, test, expect } from 'bun:test'
|
|
2
|
-
import { state,
|
|
3
|
-
|
|
4
|
-
/* === Utility Functions === */
|
|
5
|
-
|
|
6
|
-
const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
|
|
2
|
+
import { state, memo, batch, effect } from '../'
|
|
7
3
|
|
|
8
4
|
/* === Tests === */
|
|
9
5
|
|
|
@@ -12,8 +8,8 @@ describe('Batch', function () {
|
|
|
12
8
|
const cause = state(0)
|
|
13
9
|
let result = 0
|
|
14
10
|
let count = 0
|
|
15
|
-
|
|
16
|
-
result =
|
|
11
|
+
effect(() => {
|
|
12
|
+
result = cause.get()
|
|
17
13
|
count++
|
|
18
14
|
})
|
|
19
15
|
batch(() => {
|
|
@@ -29,10 +25,11 @@ describe('Batch', function () {
|
|
|
29
25
|
const a = state(3)
|
|
30
26
|
const b = state(4)
|
|
31
27
|
const c = state(5)
|
|
32
|
-
const sum =
|
|
28
|
+
const sum = memo(() => a.get() + b.get() + c.get())
|
|
33
29
|
let result = 0
|
|
34
30
|
let count = 0
|
|
35
|
-
|
|
31
|
+
effect({
|
|
32
|
+
signals: [sum],
|
|
36
33
|
ok: res => {
|
|
37
34
|
result = res
|
|
38
35
|
count++
|
|
@@ -53,10 +50,8 @@ describe('Batch', function () {
|
|
|
53
50
|
const signals = [state(2), state(3), state(5)]
|
|
54
51
|
|
|
55
52
|
// Computed: derive a calculation ...
|
|
56
|
-
const sum =
|
|
57
|
-
signals.reduce((total, v) => total + v.get(), 0)
|
|
58
|
-
).map(v => {
|
|
59
|
-
// ... perform validation and handle errors
|
|
53
|
+
const sum = memo(() => {
|
|
54
|
+
const v = signals.reduce((total, v) => total + v.get(), 0)
|
|
60
55
|
if (!Number.isFinite(v)) throw new Error('Invalid value')
|
|
61
56
|
return v
|
|
62
57
|
})
|
|
@@ -66,7 +61,8 @@ describe('Batch', function () {
|
|
|
66
61
|
let errCount = 0
|
|
67
62
|
|
|
68
63
|
// Effect: switch cases for the result
|
|
69
|
-
|
|
64
|
+
effect({
|
|
65
|
+
signals: [sum],
|
|
70
66
|
ok: v => {
|
|
71
67
|
result = v
|
|
72
68
|
okCount++
|