async-reactivity 2.0.2 → 2.0.4

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/lib/computed.js CHANGED
@@ -10,11 +10,14 @@ var ComputedState;
10
10
  class CircularDependencyError extends Error {
11
11
  }
12
12
  export default class Computed extends Tracker {
13
+ getter;
14
+ state = ComputedState.Invalid;
15
+ dependencies = new Map();
16
+ computePromise;
17
+ computePromiseActions;
18
+ lastComputeAttemptPromise;
13
19
  constructor(getter) {
14
20
  super();
15
- this.state = ComputedState.Invalid;
16
- this.dependencies = new Map();
17
- this.trackDependency = this.innerTrackDependency.bind(this);
18
21
  this.getter = getter;
19
22
  this.prepareComputePromise();
20
23
  }
@@ -91,6 +94,7 @@ export default class Computed extends Tracker {
91
94
  dependency.addDependent(this);
92
95
  return dependency.value;
93
96
  }
97
+ trackDependency = this.innerTrackDependency.bind(this);
94
98
  finalizeComputing() {
95
99
  this.state = ComputedState.Valid;
96
100
  this.lastComputeAttemptPromise = undefined;
@@ -1,12 +1,3 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
1
  import 'mocha';
11
2
  import assert from 'assert';
12
3
  import { Computed, Ref } from './index.js';
@@ -107,139 +98,119 @@ describe('computed', function () {
107
98
  });
108
99
  });
109
100
  describe('async computed', function () {
110
- it('getter', function () {
111
- return __awaiter(this, void 0, void 0, function* () {
112
- const a = new Computed(() => __awaiter(this, void 0, void 0, function* () {
113
- yield new Promise(resolve => setTimeout(resolve));
114
- return 5;
115
- }));
116
- assert.strictEqual(yield a.value, 5);
117
- });
118
- });
119
- it('tracks async dependencies', function () {
120
- return __awaiter(this, void 0, void 0, function* () {
121
- const a = new Ref(5);
122
- const b = new Computed((value) => __awaiter(this, void 0, void 0, function* () {
123
- yield new Promise(resolve => setTimeout(resolve));
124
- return value(a) + 5;
125
- }));
126
- assert.strictEqual(yield b.value, 10);
127
- a.value = 6;
128
- assert.strictEqual(yield b.value, 11);
129
- });
130
- });
131
- it('get value while computing', function () {
132
- return __awaiter(this, void 0, void 0, function* () {
133
- const a = new Computed(() => __awaiter(this, void 0, void 0, function* () {
134
- yield new Promise(resolve => setTimeout(resolve));
135
- return 5;
136
- }));
137
- a.value;
138
- assert.strictEqual(yield a.value, 5);
101
+ it('getter', async function () {
102
+ const a = new Computed(async () => {
103
+ await new Promise(resolve => setTimeout(resolve));
104
+ return 5;
139
105
  });
106
+ assert.strictEqual(await a.value, 5);
140
107
  });
141
- it('detect circular dependency', function () {
142
- return __awaiter(this, void 0, void 0, function* () {
143
- // @ts-expect-error
144
- const a = new Computed((value) => __awaiter(this, void 0, void 0, function* () {
145
- yield new Promise(resolve => setTimeout(resolve));
146
- return value(b);
147
- }));
148
- // @ts-expect-error
149
- const b = new Computed((value) => __awaiter(this, void 0, void 0, function* () {
150
- yield new Promise(resolve => setTimeout(resolve));
151
- return value(a);
152
- }));
153
- assert.rejects(() => __awaiter(this, void 0, void 0, function* () { return yield a.value; }));
154
- });
155
- });
156
- it('dependency changed while computing', function () {
157
- return __awaiter(this, void 0, void 0, function* () {
158
- const a = new Ref(5);
159
- const b = new Computed((value) => __awaiter(this, void 0, void 0, function* () { return value(a) + 5; }));
160
- b.value; // trigger compute
161
- a.value = 8;
162
- assert.strictEqual(yield b.value, 13);
163
- });
164
- });
165
- it('old dependency changed while computing', function () {
166
- return __awaiter(this, void 0, void 0, function* () {
167
- let gate = 0;
168
- const a = new Ref(5);
169
- const b = new Computed((value) => __awaiter(this, void 0, void 0, function* () {
170
- gate++;
171
- yield new Promise(resolve => setTimeout(resolve));
172
- return value(a) + 2;
173
- }));
174
- assert.strictEqual(yield b.value, 7);
175
- b.invalidate();
176
- const promise = b.value;
177
- a.value = 6;
178
- assert.strictEqual(yield promise, 8);
179
- assert.strictEqual(gate, 2);
180
- });
181
- });
182
- it('new dependency changed while computing', function () {
183
- return __awaiter(this, void 0, void 0, function* () {
184
- let gate = 0;
185
- const a = new Ref(5);
186
- const b = new Ref(10);
187
- const c = new Computed((value) => __awaiter(this, void 0, void 0, function* () {
188
- gate++;
189
- yield new Promise(resolve => setTimeout(resolve, 50));
190
- let sum = value(a);
191
- yield new Promise(resolve => setTimeout(resolve, 50));
192
- sum += value(b);
193
- return sum;
194
- }));
195
- assert.strictEqual(yield c.value, 15);
196
- c.invalidate();
197
- const promise = c.value;
198
- yield new Promise(resolve => setTimeout(resolve, 60));
199
- a.value = 10;
200
- assert.strictEqual(yield promise, 20);
201
- assert.strictEqual(gate, 3);
202
- });
203
- });
204
- it('fallback to primitive while computing', function () {
205
- return __awaiter(this, void 0, void 0, function* () {
206
- const a = new Ref(5);
207
- const b = new Ref(10);
208
- const c = new Computed((value) => __awaiter(this, void 0, void 0, function* () {
209
- if (value(a) < 10) {
210
- yield new Promise(resolve => setTimeout(resolve, 50));
211
- return value(b) + 5;
212
- }
213
- return 2;
214
- }));
215
- const promise = c.value;
216
- yield new Promise(resolve => setTimeout(resolve, 20));
217
- a.value = 10;
218
- assert.strictEqual(yield promise, 2);
108
+ it('tracks async dependencies', async function () {
109
+ const a = new Ref(5);
110
+ const b = new Computed(async (value) => {
111
+ await new Promise(resolve => setTimeout(resolve));
112
+ return value(a) + 5;
219
113
  });
114
+ assert.strictEqual(await b.value, 10);
115
+ a.value = 6;
116
+ assert.strictEqual(await b.value, 11);
220
117
  });
221
- it('throw error', function () {
222
- return __awaiter(this, void 0, void 0, function* () {
223
- const a = new Computed(() => __awaiter(this, void 0, void 0, function* () {
224
- yield new Promise(resolve => setTimeout(resolve));
225
- throw new Error();
226
- }));
227
- assert.rejects(() => a.value);
228
- });
229
- });
230
- it('dispose computed', function () {
231
- return __awaiter(this, void 0, void 0, function* () {
232
- const a = new Ref(5);
233
- let gate = 0;
234
- const b = new Computed(value => {
235
- gate++;
236
- return value(a) + 2;
237
- });
238
- b.value;
239
- assert.strictEqual(gate, 1);
240
- b.dispose();
241
- b.value;
242
- assert.strictEqual(gate, 2);
118
+ it('get value while computing', async function () {
119
+ const a = new Computed(async () => {
120
+ await new Promise(resolve => setTimeout(resolve));
121
+ return 5;
122
+ });
123
+ a.value;
124
+ assert.strictEqual(await a.value, 5);
125
+ });
126
+ it('detect circular dependency', async function () {
127
+ // @ts-expect-error
128
+ const a = new Computed(async (value) => {
129
+ await new Promise(resolve => setTimeout(resolve));
130
+ return value(b);
131
+ });
132
+ // @ts-expect-error
133
+ const b = new Computed(async (value) => {
134
+ await new Promise(resolve => setTimeout(resolve));
135
+ return value(a);
136
+ });
137
+ assert.rejects(async () => await a.value);
138
+ });
139
+ it('dependency changed while computing', async function () {
140
+ const a = new Ref(5);
141
+ const b = new Computed(async (value) => value(a) + 5);
142
+ b.value; // trigger compute
143
+ a.value = 8;
144
+ assert.strictEqual(await b.value, 13);
145
+ });
146
+ it('old dependency changed while computing', async function () {
147
+ let gate = 0;
148
+ const a = new Ref(5);
149
+ const b = new Computed(async (value) => {
150
+ gate++;
151
+ await new Promise(resolve => setTimeout(resolve));
152
+ return value(a) + 2;
153
+ });
154
+ assert.strictEqual(await b.value, 7);
155
+ b.invalidate();
156
+ const promise = b.value;
157
+ a.value = 6;
158
+ assert.strictEqual(await promise, 8);
159
+ assert.strictEqual(gate, 2);
160
+ });
161
+ it('new dependency changed while computing', async function () {
162
+ let gate = 0;
163
+ const a = new Ref(5);
164
+ const b = new Ref(10);
165
+ const c = new Computed(async (value) => {
166
+ gate++;
167
+ await new Promise(resolve => setTimeout(resolve, 50));
168
+ let sum = value(a);
169
+ await new Promise(resolve => setTimeout(resolve, 50));
170
+ sum += value(b);
171
+ return sum;
172
+ });
173
+ assert.strictEqual(await c.value, 15);
174
+ c.invalidate();
175
+ const promise = c.value;
176
+ await new Promise(resolve => setTimeout(resolve, 60));
177
+ a.value = 10;
178
+ assert.strictEqual(await promise, 20);
179
+ assert.strictEqual(gate, 3);
180
+ });
181
+ it('fallback to primitive while computing', async function () {
182
+ const a = new Ref(5);
183
+ const b = new Ref(10);
184
+ const c = new Computed(async (value) => {
185
+ if (value(a) < 10) {
186
+ await new Promise(resolve => setTimeout(resolve, 50));
187
+ return value(b) + 5;
188
+ }
189
+ return 2;
190
+ });
191
+ const promise = c.value;
192
+ await new Promise(resolve => setTimeout(resolve, 20));
193
+ a.value = 10;
194
+ assert.strictEqual(await promise, 2);
195
+ });
196
+ it('throw error', async function () {
197
+ const a = new Computed(async () => {
198
+ await new Promise(resolve => setTimeout(resolve));
199
+ throw new Error();
243
200
  });
201
+ assert.rejects(() => a.value);
202
+ });
203
+ it('dispose computed', async function () {
204
+ const a = new Ref(5);
205
+ let gate = 0;
206
+ const b = new Computed(value => {
207
+ gate++;
208
+ return value(a) + 2;
209
+ });
210
+ b.value;
211
+ assert.strictEqual(gate, 1);
212
+ b.dispose();
213
+ b.value;
214
+ assert.strictEqual(gate, 2);
244
215
  });
245
216
  });
package/lib/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { default as Computed } from './computed.js';
2
2
  export { default as Ref } from './ref.js';
3
3
  export { default as Watcher } from './watcher.js';
4
+ export { default as Listener } from './listener.js';
@@ -0,0 +1,36 @@
1
+ import Tracker from "./tracker.js";
2
+ export default class Listener extends Tracker {
3
+ init;
4
+ start;
5
+ stop;
6
+ listening = false;
7
+ constructor({ init, start, stop }) {
8
+ super();
9
+ this.init = init;
10
+ this.start = start;
11
+ this.stop = stop;
12
+ }
13
+ addDependent(dependent) {
14
+ super.addDependent(dependent);
15
+ if (!this.listening) {
16
+ this.start((value) => {
17
+ this._value = value;
18
+ this.invalidate();
19
+ });
20
+ this.listening = true;
21
+ }
22
+ }
23
+ removeDependent(dependent) {
24
+ super.removeDependent(dependent);
25
+ if (this.dependents.size === 0) {
26
+ this.stop();
27
+ this.listening = false;
28
+ }
29
+ }
30
+ get value() {
31
+ if (this._value === undefined) {
32
+ this._value = this.init();
33
+ }
34
+ return this._value;
35
+ }
36
+ }
@@ -0,0 +1,85 @@
1
+ import 'mocha';
2
+ import assert from 'assert';
3
+ import { mock } from 'node:test';
4
+ import { Watcher, Listener } from './index.js';
5
+ describe('listener', function () {
6
+ it('wait for dependent', function () {
7
+ assert.doesNotThrow(() => {
8
+ new Listener({
9
+ init: () => { throw new Error(); },
10
+ start: () => { throw new Error(); },
11
+ stop: () => { throw new Error(); },
12
+ });
13
+ });
14
+ });
15
+ it('init', function () {
16
+ const listener = new Listener({
17
+ init: () => 1,
18
+ start: () => { throw new Error(); },
19
+ stop: () => { throw new Error(); },
20
+ });
21
+ assert.strictEqual(listener.value, 1);
22
+ });
23
+ it('start', async function () {
24
+ const listener = new Listener({
25
+ init: () => 1,
26
+ start: (setter) => {
27
+ setTimeout(() => {
28
+ setter(2);
29
+ }, 10);
30
+ },
31
+ stop: () => { },
32
+ });
33
+ const onChange = mock.fn();
34
+ new Watcher(listener, onChange);
35
+ assert.strictEqual(onChange.mock.callCount(), 1);
36
+ assert.deepStrictEqual(onChange.mock.calls[0].arguments, [1]);
37
+ await new Promise((resolve) => setTimeout(resolve, 20));
38
+ assert.strictEqual(onChange.mock.callCount(), 2);
39
+ assert.deepStrictEqual(onChange.mock.calls[1].arguments, [2, 1]);
40
+ });
41
+ it('stop', async function () {
42
+ let gate = false;
43
+ let timeout;
44
+ const listener = new Listener({
45
+ init: () => 1,
46
+ start: (setter) => {
47
+ timeout = setTimeout(() => {
48
+ gate = true;
49
+ setter(2);
50
+ }, 10);
51
+ },
52
+ stop: () => {
53
+ clearTimeout(timeout);
54
+ },
55
+ });
56
+ const w = new Watcher(listener, () => { });
57
+ await new Promise((resolve) => setTimeout(resolve, 5));
58
+ w.dispose();
59
+ await new Promise((resolve) => setTimeout(resolve, 10));
60
+ assert.strictEqual(gate, false);
61
+ });
62
+ it('stop + start', async function () {
63
+ let timeout;
64
+ const listener = new Listener({
65
+ init: () => 1,
66
+ start: (setter) => {
67
+ timeout = setTimeout(() => {
68
+ setter(2);
69
+ }, 10);
70
+ },
71
+ stop: () => {
72
+ clearTimeout(timeout);
73
+ },
74
+ });
75
+ const onChange = mock.fn();
76
+ const w1 = new Watcher(listener, onChange);
77
+ assert.strictEqual(onChange.mock.callCount(), 1);
78
+ await new Promise((resolve) => setTimeout(resolve, 5));
79
+ w1.dispose();
80
+ new Watcher(listener, onChange);
81
+ assert.strictEqual(onChange.mock.callCount(), 2);
82
+ await new Promise((resolve) => setTimeout(resolve, 15));
83
+ assert.strictEqual(onChange.mock.callCount(), 3);
84
+ });
85
+ });
package/lib/ref.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import Tracker from "./tracker.js";
2
2
  const defaultIsEqual = (v1, v2) => v1 === v2;
3
3
  export default class Ref extends Tracker {
4
+ isEqual;
4
5
  constructor(_value, isEqual = (defaultIsEqual)) {
5
6
  super();
6
7
  this._value = _value;
package/lib/tracker.js CHANGED
@@ -1,7 +1,6 @@
1
1
  export default class Tracker {
2
- constructor() {
3
- this.dependents = new Set();
4
- }
2
+ dependents = new Set();
3
+ _value;
5
4
  addDependent(dependent) {
6
5
  this.dependents.add(dependent);
7
6
  }
package/lib/watcher.js CHANGED
@@ -6,9 +6,11 @@ var WatchState;
6
6
  })(WatchState || (WatchState = {}));
7
7
  ;
8
8
  export default class Watcher extends Tracker {
9
+ onChange;
10
+ dependency;
11
+ state = WatchState.Valid;
9
12
  constructor(dependency, onChange, immediate = true) {
10
13
  super();
11
- this.state = WatchState.Valid;
12
14
  this.onChange = onChange;
13
15
  this.dependency = dependency;
14
16
  dependency.addDependent(this);
@@ -18,13 +20,18 @@ export default class Watcher extends Tracker {
18
20
  }
19
21
  }
20
22
  invalidate() {
21
- this.state = WatchState.Uncertain;
22
- const oldValue = this._value;
23
- this._value = this.dependency.value;
24
23
  if (this.state === WatchState.Uncertain) {
25
- this.onChange(this._value, oldValue);
26
- this.state = WatchState.Valid;
24
+ return;
27
25
  }
26
+ this.state = WatchState.Uncertain;
27
+ Promise.resolve().then(() => {
28
+ const oldValue = this._value;
29
+ this._value = this.dependency.value;
30
+ if (this.state === WatchState.Uncertain) {
31
+ this.onChange(this._value, oldValue);
32
+ this.state = WatchState.Valid;
33
+ }
34
+ });
28
35
  }
29
36
  validate() {
30
37
  this.state = WatchState.Valid;
@@ -1,12 +1,3 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
1
  import 'mocha';
11
2
  import assert from 'assert';
12
3
  import { Computed, Ref, Watcher } from './index.js';
@@ -19,55 +10,68 @@ describe('watcher', function () {
19
10
  }, false);
20
11
  a.value = 6;
21
12
  });
22
- it('async', function () {
23
- return __awaiter(this, void 0, void 0, function* () {
24
- const a = new Computed(() => __awaiter(this, void 0, void 0, function* () {
25
- yield new Promise(resolve => setTimeout(resolve));
26
- return 10;
27
- }));
28
- const result = yield new Promise(resolve => new Watcher(a, resolve));
29
- assert.strictEqual(result, 10);
13
+ it('sync debounce', async function () {
14
+ const a = new Ref(5);
15
+ let gate = 0;
16
+ new Watcher(a, () => {
17
+ gate++;
18
+ }, false);
19
+ a.value = 6;
20
+ await new Promise(resolve => setTimeout(resolve));
21
+ assert.strictEqual(gate, 1);
22
+ a.value = 7;
23
+ a.value = 8;
24
+ a.value = 9;
25
+ await new Promise(resolve => setTimeout(resolve));
26
+ assert.strictEqual(gate, 2);
27
+ });
28
+ it('async', async function () {
29
+ const a = new Computed(async () => {
30
+ await new Promise(resolve => setTimeout(resolve));
31
+ return 10;
30
32
  });
33
+ const result = await new Promise(resolve => new Watcher(a, resolve));
34
+ assert.strictEqual(result, 10);
31
35
  });
32
- it('sync cancel', function () {
33
- return __awaiter(this, void 0, void 0, function* () {
34
- const a = new Ref(5);
35
- const b = new Computed((value) => {
36
- return value(a) % 2;
37
- });
38
- let gate = 0;
39
- new Watcher(b, () => {
40
- gate++;
41
- }, false);
42
- a.value = 6;
43
- assert.strictEqual(gate, 1);
44
- a.value = 7;
45
- a.value = 9;
46
- a.value = 11;
47
- assert.strictEqual(gate, 2);
36
+ it('sync cancel', async function () {
37
+ const a = new Ref(5);
38
+ const b = new Computed((value) => {
39
+ return value(a) % 2;
48
40
  });
41
+ let gate = 0;
42
+ new Watcher(b, () => {
43
+ gate++;
44
+ }, false);
45
+ a.value = 6;
46
+ await new Promise(resolve => setTimeout(resolve));
47
+ assert.strictEqual(gate, 1);
48
+ a.value = 7;
49
+ a.value = 9;
50
+ a.value = 11;
51
+ await new Promise(resolve => setTimeout(resolve));
52
+ assert.strictEqual(gate, 2);
49
53
  });
50
- it('async sync cancel', function () {
51
- return __awaiter(this, void 0, void 0, function* () {
52
- const a = new Ref(5);
53
- const b = new Computed((value) => __awaiter(this, void 0, void 0, function* () {
54
- new Promise(resolve => setTimeout(resolve));
55
- return value(a) % 2;
56
- }));
57
- let gate = 0;
58
- new Watcher(b, () => {
59
- gate++;
60
- }, false);
61
- yield new Promise(resolve => setTimeout(resolve, 10));
62
- a.value = 6;
63
- yield new Promise(resolve => setTimeout(resolve, 10));
64
- assert.strictEqual(gate, 1);
65
- a.value = 7;
66
- yield new Promise(resolve => setTimeout(resolve, 10));
67
- assert.strictEqual(gate, 2);
68
- a.value = 9;
69
- yield new Promise(resolve => setTimeout(resolve, 10));
70
- assert.strictEqual(gate, 3);
54
+ it('async sync cancel', async function () {
55
+ const a = new Ref(5);
56
+ let computedGate = 0;
57
+ const b = new Computed(async (value) => {
58
+ computedGate++;
59
+ new Promise(resolve => setTimeout(resolve));
60
+ return value(a) % 2;
71
61
  });
62
+ let watcherGate = 0;
63
+ new Watcher(b, () => {
64
+ watcherGate++;
65
+ }, false);
66
+ await new Promise(resolve => setTimeout(resolve, 10));
67
+ a.value = 6;
68
+ await new Promise(resolve => setTimeout(resolve, 10));
69
+ assert.strictEqual(computedGate, 2);
70
+ assert.strictEqual(watcherGate, 1);
71
+ a.value = 7;
72
+ a.value = 9;
73
+ await new Promise(resolve => setTimeout(resolve, 10));
74
+ assert.strictEqual(computedGate, 3);
75
+ assert.strictEqual(watcherGate, 2);
72
76
  });
73
77
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "async-reactivity",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "types": "types/index.d.ts",
@@ -22,9 +22,9 @@
22
22
  "author": "Donatas Lučiūnas",
23
23
  "license": "ISC",
24
24
  "devDependencies": {
25
- "@types/mocha": "^10.0.7",
26
- "@types/node": "^20.14.10",
27
- "mocha": "^10.6.0",
28
- "typescript": "^5.5.3"
25
+ "@types/mocha": "^10.0.8",
26
+ "@types/node": "^20.14.15",
27
+ "mocha": "^10.7.3",
28
+ "typescript": "^5.6.2"
29
29
  }
30
30
  }
package/src/index.ts CHANGED
@@ -2,4 +2,5 @@ export { default as Computed, ComputeFunc, ComputeFuncScoped } from './computed.
2
2
  export { default as Ref } from './ref.js';
3
3
  export { default as Watcher } from './watcher.js';
4
4
  export { default as Dependency } from './dependency.js';
5
- export { default as Dependent } from './dependent.js';
5
+ export { default as Dependent } from './dependent.js';
6
+ export { default as Listener } from './listener.js';
@@ -0,0 +1,106 @@
1
+ import 'mocha';
2
+ import assert from 'assert';
3
+ import { mock } from 'node:test';
4
+ import { Watcher, Listener } from './index.js';
5
+
6
+ describe('listener', function () {
7
+ it('wait for dependent', function () {
8
+ assert.doesNotThrow(() => {
9
+ new Listener({
10
+ init: () => { throw new Error(); },
11
+ start: () => { throw new Error(); },
12
+ stop: () => { throw new Error(); },
13
+ });
14
+ });
15
+ });
16
+
17
+ it('init', function () {
18
+ const listener = new Listener({
19
+ init: () => 1,
20
+ start: () => { throw new Error(); },
21
+ stop: () => { throw new Error(); },
22
+ });
23
+
24
+ assert.strictEqual(listener.value, 1);
25
+ });
26
+
27
+ it('start', async function () {
28
+ const listener = new Listener({
29
+ init: () => 1,
30
+ start: (setter) => {
31
+ setTimeout(() => {
32
+ setter(2);
33
+ }, 10);
34
+ },
35
+ stop: () => { },
36
+ });
37
+
38
+ const onChange = mock.fn();
39
+ new Watcher(listener, onChange);
40
+
41
+ assert.strictEqual(onChange.mock.callCount(), 1);
42
+ assert.deepStrictEqual(onChange.mock.calls[0].arguments, [1]);
43
+
44
+ await new Promise((resolve) => setTimeout(resolve, 20));
45
+
46
+ assert.strictEqual(onChange.mock.callCount(), 2);
47
+ assert.deepStrictEqual(onChange.mock.calls[1].arguments, [2, 1]);
48
+ });
49
+
50
+ it('stop', async function () {
51
+ let gate = false;
52
+ let timeout: NodeJS.Timeout;
53
+ const listener = new Listener({
54
+ init: () => 1,
55
+ start: (setter) => {
56
+ timeout = setTimeout(() => {
57
+ gate = true;
58
+ setter(2);
59
+ }, 10);
60
+ },
61
+ stop: () => {
62
+ clearTimeout(timeout);
63
+ },
64
+ });
65
+
66
+ const w = new Watcher(listener, () => {});
67
+
68
+ await new Promise((resolve) => setTimeout(resolve, 5));
69
+
70
+ w.dispose();
71
+
72
+ await new Promise((resolve) => setTimeout(resolve, 10));
73
+
74
+ assert.strictEqual(gate, false);
75
+ });
76
+
77
+ it('stop + start', async function () {
78
+ let timeout: NodeJS.Timeout;
79
+ const listener = new Listener({
80
+ init: () => 1,
81
+ start: (setter) => {
82
+ timeout = setTimeout(() => {
83
+ setter(2);
84
+ }, 10);
85
+ },
86
+ stop: () => {
87
+ clearTimeout(timeout);
88
+ },
89
+ });
90
+
91
+ const onChange = mock.fn();
92
+ const w1 = new Watcher(listener, onChange);
93
+ assert.strictEqual(onChange.mock.callCount(), 1);
94
+
95
+ await new Promise((resolve) => setTimeout(resolve, 5));
96
+
97
+ w1.dispose();
98
+
99
+ new Watcher(listener, onChange);
100
+ assert.strictEqual(onChange.mock.callCount(), 2);
101
+
102
+ await new Promise((resolve) => setTimeout(resolve, 15));
103
+
104
+ assert.strictEqual(onChange.mock.callCount(), 3);
105
+ });
106
+ });
@@ -0,0 +1,44 @@
1
+ import Dependency from "./dependency.js";
2
+ import Dependent from "./dependent.js";
3
+ import Tracker from "./tracker.js";
4
+
5
+ export default class Listener<T> extends Tracker<T> implements Dependency<T> {
6
+ private init: () => T;
7
+ private start: (setter: (value: T) => void) => void;
8
+ private stop: () => void;
9
+ private listening = false;
10
+
11
+ constructor({ init, start, stop }: { init: () => T, start: (setter: (value: T) => void) => void, stop: () => void }) {
12
+ super();
13
+
14
+ this.init = init;
15
+ this.start = start;
16
+ this.stop = stop;
17
+ }
18
+
19
+ public addDependent(dependent: Dependent): void {
20
+ super.addDependent(dependent);
21
+ if (!this.listening) {
22
+ this.start((value) => {
23
+ this._value = value;
24
+ this.invalidate();
25
+ });
26
+ this.listening = true;
27
+ }
28
+ }
29
+
30
+ public removeDependent(dependent: Dependent): void {
31
+ super.removeDependent(dependent);
32
+ if (this.dependents.size === 0) {
33
+ this.stop();
34
+ this.listening = false;
35
+ }
36
+ }
37
+
38
+ public get value() {
39
+ if (this._value === undefined) {
40
+ this._value = this.init();
41
+ }
42
+ return this._value;
43
+ }
44
+ }
package/src/tracker.ts CHANGED
@@ -1,7 +1,4 @@
1
- import Computed from "./computed.js";
2
- import Watcher from "./watcher.js";
3
-
4
- type Dependent = Computed<any> | Watcher<any>;
1
+ import Dependent from "./dependent.js";
5
2
 
6
3
  export default class Tracker<T> {
7
4
  protected dependents = new Set<Dependent>();
package/src/tsconfig.json CHANGED
@@ -9,7 +9,7 @@
9
9
  // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
10
10
  // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
11
11
  /* Language and Environment */
12
- "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
12
+ "target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
13
13
  // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
14
14
  // "jsx": "preserve", /* Specify what JSX code is generated. */
15
15
  // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
@@ -12,6 +12,22 @@ describe('watcher', function () {
12
12
  a.value = 6;
13
13
  });
14
14
 
15
+ it('sync debounce', async function () {
16
+ const a = new Ref(5);
17
+ let gate = 0;
18
+ new Watcher(a, () => {
19
+ gate++;
20
+ }, false);
21
+ a.value = 6;
22
+ await new Promise(resolve => setTimeout(resolve));
23
+ assert.strictEqual(gate, 1);
24
+ a.value = 7;
25
+ a.value = 8;
26
+ a.value = 9;
27
+ await new Promise(resolve => setTimeout(resolve));
28
+ assert.strictEqual(gate, 2);
29
+ });
30
+
15
31
  it('async', async function () {
16
32
  const a = new Computed(async () => {
17
33
  await new Promise(resolve => setTimeout(resolve));
@@ -31,32 +47,36 @@ describe('watcher', function () {
31
47
  gate++;
32
48
  }, false);
33
49
  a.value = 6;
50
+ await new Promise(resolve => setTimeout(resolve));
34
51
  assert.strictEqual(gate, 1);
35
52
  a.value = 7;
36
53
  a.value = 9;
37
54
  a.value = 11;
55
+ await new Promise(resolve => setTimeout(resolve));
38
56
  assert.strictEqual(gate, 2);
39
57
  });
40
58
 
41
59
  it('async sync cancel', async function () {
42
60
  const a = new Ref(5);
61
+ let computedGate = 0;
43
62
  const b = new Computed(async (value) => {
63
+ computedGate++;
44
64
  new Promise(resolve => setTimeout(resolve));
45
65
  return value(a) % 2;
46
66
  });
47
- let gate = 0;
67
+ let watcherGate = 0;
48
68
  new Watcher(b, () => {
49
- gate++;
69
+ watcherGate++;
50
70
  }, false);
51
71
  await new Promise(resolve => setTimeout(resolve, 10));
52
72
  a.value = 6;
53
73
  await new Promise(resolve => setTimeout(resolve, 10));
54
- assert.strictEqual(gate, 1);
74
+ assert.strictEqual(computedGate, 2);
75
+ assert.strictEqual(watcherGate, 1);
55
76
  a.value = 7;
56
- await new Promise(resolve => setTimeout(resolve, 10));
57
- assert.strictEqual(gate, 2);
58
77
  a.value = 9;
59
78
  await new Promise(resolve => setTimeout(resolve, 10));
60
- assert.strictEqual(gate, 3);
79
+ assert.strictEqual(computedGate, 3);
80
+ assert.strictEqual(watcherGate, 2);
61
81
  });
62
82
  });
package/src/watcher.ts CHANGED
@@ -28,13 +28,18 @@ export default class Watcher<T> extends Tracker<T> implements Dependent {
28
28
  }
29
29
 
30
30
  public invalidate() {
31
- this.state = WatchState.Uncertain;
32
- const oldValue = this._value;
33
- this._value = this.dependency.value;
34
31
  if (this.state === WatchState.Uncertain) {
35
- this.onChange(this._value, oldValue);
36
- this.state = WatchState.Valid;
32
+ return;
37
33
  }
34
+ this.state = WatchState.Uncertain;
35
+ Promise.resolve().then(() => {
36
+ const oldValue = this._value;
37
+ this._value = this.dependency.value;
38
+ if (this.state === WatchState.Uncertain) {
39
+ this.onChange(this._value, oldValue);
40
+ this.state = WatchState.Valid;
41
+ }
42
+ });
38
43
  }
39
44
 
40
45
  public validate() {
package/types/index.d.ts CHANGED
@@ -3,4 +3,5 @@ export { default as Ref } from './ref.js';
3
3
  export { default as Watcher } from './watcher.js';
4
4
  export { default as Dependency } from './dependency.js';
5
5
  export { default as Dependent } from './dependent.js';
6
+ export { default as Listener } from './listener.js';
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACpF,OAAO,EAAE,OAAO,IAAI,GAAG,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACpF,OAAO,EAAE,OAAO,IAAI,GAAG,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,18 @@
1
+ import Dependency from "./dependency.js";
2
+ import Dependent from "./dependent.js";
3
+ import Tracker from "./tracker.js";
4
+ export default class Listener<T> extends Tracker<T> implements Dependency<T> {
5
+ private init;
6
+ private start;
7
+ private stop;
8
+ private listening;
9
+ constructor({ init, start, stop }: {
10
+ init: () => T;
11
+ start: (setter: (value: T) => void) => void;
12
+ stop: () => void;
13
+ });
14
+ addDependent(dependent: Dependent): void;
15
+ removeDependent(dependent: Dependent): void;
16
+ get value(): T;
17
+ }
18
+ //# sourceMappingURL=listener.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listener.d.ts","sourceRoot":"","sources":["../src/listener.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,iBAAiB,CAAC;AACzC,OAAO,SAAS,MAAM,gBAAgB,CAAC;AACvC,OAAO,OAAO,MAAM,cAAc,CAAC;AAEnC,MAAM,CAAC,OAAO,OAAO,QAAQ,CAAC,CAAC,CAAE,SAAQ,OAAO,CAAC,CAAC,CAAE,YAAW,UAAU,CAAC,CAAC,CAAC;IACxE,OAAO,CAAC,IAAI,CAAU;IACtB,OAAO,CAAC,KAAK,CAAuC;IACpD,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,SAAS,CAAS;gBAEd,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QAAC,KAAK,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,KAAK,IAAI,CAAC;QAAC,IAAI,EAAE,MAAM,IAAI,CAAA;KAAE;IAQ5G,YAAY,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI;IAWxC,eAAe,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI;IAQlD,IAAW,KAAK,MAKf;CACJ"}
@@ -0,0 +1,2 @@
1
+ import 'mocha';
2
+ //# sourceMappingURL=listener.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listener.test.d.ts","sourceRoot":"","sources":["../src/listener.test.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,CAAC"}
@@ -1,6 +1,4 @@
1
- import Computed from "./computed.js";
2
- import Watcher from "./watcher.js";
3
- type Dependent = Computed<any> | Watcher<any>;
1
+ import Dependent from "./dependent.js";
4
2
  export default class Tracker<T> {
5
3
  protected dependents: Set<Dependent>;
6
4
  protected _value?: T;
@@ -9,5 +7,4 @@ export default class Tracker<T> {
9
7
  invalidate(): void;
10
8
  get value(): T | undefined;
11
9
  }
12
- export {};
13
10
  //# sourceMappingURL=tracker.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tracker.d.ts","sourceRoot":"","sources":["../src/tracker.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,eAAe,CAAC;AACrC,OAAO,OAAO,MAAM,cAAc,CAAC;AAEnC,KAAK,SAAS,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;AAE9C,MAAM,CAAC,OAAO,OAAO,OAAO,CAAC,CAAC;IAC1B,SAAS,CAAC,UAAU,iBAAwB;IAC5C,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAEd,YAAY,CAAC,SAAS,EAAE,SAAS;IAIjC,eAAe,CAAC,SAAS,EAAE,SAAS;IAIpC,UAAU,IAAI,IAAI;IAMzB,IAAW,KAAK,kBAEf;CACJ"}
1
+ {"version":3,"file":"tracker.d.ts","sourceRoot":"","sources":["../src/tracker.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,gBAAgB,CAAC;AAEvC,MAAM,CAAC,OAAO,OAAO,OAAO,CAAC,CAAC;IAC1B,SAAS,CAAC,UAAU,iBAAwB;IAC5C,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAEd,YAAY,CAAC,SAAS,EAAE,SAAS;IAIjC,eAAe,CAAC,SAAS,EAAE,SAAS;IAIpC,UAAU,IAAI,IAAI;IAMzB,IAAW,KAAK,kBAEf;CACJ"}
@@ -1 +1 @@
1
- {"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,iBAAiB,CAAC;AACzC,OAAO,SAAS,MAAM,gBAAgB,CAAC;AACvC,OAAO,OAAO,MAAM,cAAc,CAAC;AAOnC,KAAK,YAAY,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC,KAAK,IAAI,CAAC;AAE3D,MAAM,CAAC,OAAO,OAAO,OAAO,CAAC,CAAC,CAAE,SAAQ,OAAO,CAAC,CAAC,CAAE,YAAW,SAAS;IAEnE,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,UAAU,CAAgB;IAClC,OAAO,CAAC,KAAK,CAAoB;gBAErB,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,SAAS,GAAE,OAAc;IAYpF,UAAU;IAUV,QAAQ;IAIR,OAAO;CAGjB"}
1
+ {"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,iBAAiB,CAAC;AACzC,OAAO,SAAS,MAAM,gBAAgB,CAAC;AACvC,OAAO,OAAO,MAAM,cAAc,CAAC;AAOnC,KAAK,YAAY,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC,KAAK,IAAI,CAAC;AAE3D,MAAM,CAAC,OAAO,OAAO,OAAO,CAAC,CAAC,CAAE,SAAQ,OAAO,CAAC,CAAC,CAAE,YAAW,SAAS;IAEnE,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,UAAU,CAAgB;IAClC,OAAO,CAAC,KAAK,CAAoB;gBAErB,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,SAAS,GAAE,OAAc;IAYpF,UAAU;IAeV,QAAQ;IAIR,OAAO;CAGjB"}