-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
/
autorun.ts
119 lines (106 loc) · 3.25 KB
/
autorun.ts
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
119
import {Lambda, getNextId, deprecated, invariant} from "../utils/utils";
import {assertUnwrapped} from "../types/modifiers";
import {Reaction} from "../core/reaction";
import {untracked} from "../core/derivation";
import {action} from "../core/action";
/**
* Creates a reactive view and keeps it alive, so that the view is always
* updated if one of the dependencies changes, even when the view is not further used by something else.
* @param view The reactive view
* @param scope (optional)
* @returns disposer function, which can be used to stop the view from being updated in the future.
*/
export function autorun(view: Lambda, scope?: any) {
assertUnwrapped(view, "autorun methods cannot have modifiers");
invariant(typeof view === "function", "autorun expects a function");
invariant(view.length === 0, "autorun expects a function without arguments");
if (scope)
view = view.bind(scope);
const reaction = new Reaction(view.name || ("Autorun@" + getNextId()), function () {
this.track(view);
});
reaction.schedule();
return reaction.getDisposer();
}
/**
* Similar to 'observer', observes the given predicate until it returns true.
* Once it returns true, the 'effect' function is invoked an the observation is cancelled.
* @param predicate
* @param effect
* @param scope (optional)
* @returns disposer function to prematurely end the observer.
*/
export function when(predicate: () => boolean, effect: Lambda, scope?: any) {
let disposeImmediately = false;
const disposer = autorun(() => {
if (predicate.call(scope)) {
if (disposer)
disposer();
else
disposeImmediately = true;
untracked(() => effect.call(scope));
}
});
if (disposeImmediately)
disposer();
return disposer;
}
export function autorunUntil(predicate: () => boolean, effect: Lambda, scope?: any) {
deprecated("`autorunUntil` is deprecated, please use `when`.");
return when.apply(null, arguments);
}
export function autorunAsync(func: Lambda, delay: number = 1, scope?: any) {
if (scope)
func = func.bind(scope);
let isScheduled = false;
const r = new Reaction(func.name || ("AutorunAsync@" + getNextId()), () => {
if (!isScheduled) {
isScheduled = true;
setTimeout(() => {
isScheduled = false;
if (!r.isDisposed)
r.track(func);
}, delay);
}
});
r.schedule();
return r.getDisposer();
}
/**
*
* Basically sugar for computed(expr).observe(action(effect))
* or
* autorun(() => action(effect)(expr));
*/
export function reaction<T>(expression: () => T, effect: (arg: T) => void, fireImmediately = false, delay = 0, scope?: any) {
const name = (expression as any).name || (effect as any).name || ("Reaction@" + getNextId());
if (scope) {
expression = expression.bind(scope);
effect = action(name, effect.bind(scope));
}
let firstTime = true;
let isScheduled = false;
function reactionRunner () {
if (r.isDisposed)
return;
let nextValue;
r.track(() => nextValue = expression());
if (!firstTime || fireImmediately)
effect(nextValue);
if (firstTime)
firstTime = false;
}
const r = new Reaction(name, () => {
if (delay < 1) {
reactionRunner();
} else if (!isScheduled) {
isScheduled = true;
setTimeout(() => {
isScheduled = false;
reactionRunner();
}, delay);
}
});
r.schedule();
return r.getDisposer();
}