async-reactivity 2.0.26 → 2.0.28

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 CHANGED
@@ -35,8 +35,8 @@ To integrate state with reactive state you can use:
35
35
  * `start` function is called when listener gets first dependent
36
36
  * it ensures value is updated
37
37
  * `stop` function is called when listener loses last dependent
38
- * [watcher](./src/watcher.ts)
39
- * a function that is called when dependency changes
38
+ * [watcher](./src/watcher.ts) and [effect](./src/effect.ts)
39
+ * a function that is called when dependency changes (Vue.js `watch` and `watchEffect` equivalent)
40
40
  * can only be dependent (only outgoing arrows)
41
41
 
42
42
  # Behavior
package/lib/computed.js CHANGED
@@ -1,31 +1,21 @@
1
- import Tracker from "./tracker.js";
2
1
  import defaultIsEqual from "./defaultIsEqual.js";
3
2
  import { Deferrer } from "./deferrer.js";
4
3
  import { debounce } from 'lodash-es';
5
- var ComputedState;
6
- (function (ComputedState) {
7
- ComputedState[ComputedState["Invalid"] = 0] = "Invalid";
8
- ComputedState[ComputedState["Valid"] = 1] = "Valid";
9
- ComputedState[ComputedState["Uncertain"] = 2] = "Uncertain";
10
- ComputedState[ComputedState["Computing"] = 3] = "Computing";
11
- })(ComputedState || (ComputedState = {}));
4
+ import Effect, { EffectState, InSyncSymbol } from "./effect.js";
12
5
  class CircularDependencyError extends Error {
13
6
  }
14
- export default class Computed extends Tracker {
7
+ export default class Computed extends Effect {
8
+ _value;
15
9
  getter;
16
10
  isEqual;
17
- state = ComputedState.Invalid;
18
- dependencies = new Map(); // boolean indicates whether value is in sync with the dependency
19
- computePromise;
20
- computePromiseActions;
21
- lastComputeAttemptPromise;
11
+ dependents = new Set();
22
12
  deferrer;
23
- abortController;
24
13
  constructor(getter, isEqual = (defaultIsEqual), timeToLive) {
25
- super();
14
+ super(((value, _firstRun, abortSignal) => {
15
+ return getter(value, this._value, abortSignal);
16
+ }), true);
26
17
  this.getter = getter;
27
18
  this.isEqual = isEqual;
28
- this.prepareComputePromise();
29
19
  if (timeToLive !== undefined) {
30
20
  this.deferrer = new Deferrer(debounce(() => {
31
21
  if (this.dependents.size === 0) {
@@ -34,137 +24,59 @@ export default class Computed extends Tracker {
34
24
  }, timeToLive));
35
25
  }
36
26
  }
37
- prepareComputePromise() {
38
- this.computePromise = new Promise((resolve, reject) => {
39
- this.computePromiseActions = {
40
- resolve,
41
- reject
42
- };
43
- });
44
- }
45
27
  get value() {
46
- if (this.state === ComputedState.Invalid || this.state === ComputedState.Uncertain) {
47
- return this.compute();
48
- }
49
- return this._value;
50
- }
51
- compute() {
52
- if (this.state === ComputedState.Uncertain) {
53
- const inSync = [...this.dependencies.entries()].every(([d, inSync]) => {
54
- if (!inSync && d instanceof Computed && d.state === ComputedState.Uncertain) {
55
- d.value;
56
- return this.dependencies.get(d);
28
+ if (this.state === EffectState.Initial || this.state === EffectState.Scheduled) {
29
+ const oldValue = this._value;
30
+ const newValue = this.run();
31
+ if (newValue !== InSyncSymbol) {
32
+ this._value = newValue;
33
+ if (this.isEqual(this._value, oldValue)) {
34
+ this.validateDependents();
57
35
  }
58
- return inSync;
59
- });
60
- if (inSync) {
61
- this.finalizeComputing();
62
- this.validateDependents();
63
- return this._value;
64
36
  }
65
37
  }
66
- else if (this.state === ComputedState.Computing) {
67
- this.abortController?.abort();
68
- }
69
- this.state = ComputedState.Computing;
70
- this.clearDependencies(true);
71
- this.abortController = new AbortController();
72
- const newValue = this.getter(this.trackDependency, this._value, this.abortController.signal);
73
- if (this.isEqual(newValue, this._value)) {
74
- this.handlePromiseThen(this.lastComputeAttemptPromise, this._value);
75
- this.validateDependents();
76
- }
77
- else if (newValue instanceof Promise) {
78
- const computeAttemptPromise = newValue
79
- .then(result => this.handlePromiseThen(computeAttemptPromise, result))
80
- .catch(error => this.handlePromiseCatch(computeAttemptPromise, error));
81
- this.lastComputeAttemptPromise = computeAttemptPromise;
82
- this._value = this.computePromise;
83
- }
84
- else {
85
- this._value = newValue;
86
- this.handlePromiseThen(this.lastComputeAttemptPromise, this._value);
87
- }
88
38
  return this._value;
89
39
  }
90
- clearDependencies(compute) {
91
- for (const dependency of this.dependencies.keys()) {
92
- dependency.removeDependent(this, compute ? this.computePromise : undefined);
93
- }
94
- this.dependencies.clear();
95
- }
96
- handlePromiseThen(computeAttemptPromise, result) {
97
- if (this.lastComputeAttemptPromise === computeAttemptPromise) {
98
- this.computePromiseActions.resolve(result);
99
- this.finalizeComputing();
100
- }
101
- }
102
- handlePromiseCatch(computeAttemptPromise, error) {
103
- if (this.lastComputeAttemptPromise === computeAttemptPromise) {
104
- this.computePromiseActions.reject(error);
105
- this.finalizeComputing();
40
+ validateDependents() {
41
+ for (const dependent of this.dependents.keys()) {
42
+ dependent.validate(this);
106
43
  }
107
44
  }
108
- innerTrackDependency(dependency) {
109
- if (dependency === undefined) {
110
- return undefined;
111
- }
112
- if (this.dependents.has(dependency)) {
45
+ addDependent(dependent) {
46
+ if (this.dependencies.has(dependent)) {
113
47
  throw new CircularDependencyError();
114
48
  }
115
- this.dependencies.set(dependency, true);
116
- dependency.addDependent(this);
117
- return dependency.value;
49
+ this.dependents.add(dependent);
118
50
  }
119
- trackDependency = this.innerTrackDependency.bind(this);
120
- finalizeComputing() {
121
- this.state = ComputedState.Valid;
122
- this.lastComputeAttemptPromise = undefined;
123
- this.abortController = undefined;
124
- this.prepareComputePromise();
51
+ removeDependent(dependent, promise = Promise.resolve()) {
52
+ this.dependents.delete(dependent);
53
+ this.deferrer?.finally(promise);
125
54
  }
126
55
  invalidate(dependency) {
127
- if (dependency) {
128
- this.dependencies.set(dependency, false);
129
- }
130
- else {
131
- for (const dep of this.dependencies.keys()) {
132
- this.dependencies.set(dep, false);
56
+ if (this.state === EffectState.Waiting) {
57
+ this.state = EffectState.Scheduled;
58
+ for (const dependent of this.dependents) {
59
+ dependent.invalidate(this);
133
60
  }
134
61
  }
135
- if (this.state === ComputedState.Computing) {
136
- this.lastComputeAttemptPromise = undefined; // prevent finalizeComputing if it is in the event loop already
137
- Promise.resolve().then(this.compute.bind(this));
138
- }
139
- else if (this.state === ComputedState.Valid) {
140
- this.state = ComputedState.Uncertain;
141
- super.invalidate();
142
- }
62
+ super.invalidate(dependency);
143
63
  }
144
64
  forceInvalidate() {
145
- this.invalidate();
146
- if (this.state !== ComputedState.Computing) {
147
- this.state = ComputedState.Invalid;
65
+ if (this.dependencies.size === 0) {
66
+ this.invalidate();
148
67
  }
149
- }
150
- validate(dependency) {
151
- this.dependencies.set(dependency, true);
152
- }
153
- validateDependents() {
154
- for (const dependent of this.dependents.keys()) {
155
- dependent.validate(this);
68
+ else {
69
+ for (const dependency of this.dependencies.keys()) {
70
+ this.invalidate(dependency);
71
+ }
72
+ }
73
+ if (this.state !== EffectState.Running) {
74
+ this.state = EffectState.Initial;
156
75
  }
157
- }
158
- removeDependent(dependent, promise = Promise.resolve()) {
159
- super.removeDependent(dependent);
160
- this.deferrer?.finally(promise);
161
76
  }
162
77
  reset() {
163
- this.clearDependencies(false);
164
- this.state = ComputedState.Invalid;
165
78
  this._value = undefined;
166
- this.lastComputeAttemptPromise = undefined;
167
- this.prepareComputePromise();
79
+ super.dispose();
168
80
  }
169
81
  dispose() {
170
82
  this.reset();