async-reactivity 2.0.0 → 2.0.2
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 +124 -123
- package/lib/computed.test.js +245 -0
- package/lib/dependency.js +1 -1
- package/lib/dependent.js +1 -1
- package/lib/index.js +3 -3
- package/lib/ref.js +19 -17
- package/lib/ref.test.js +21 -0
- package/lib/tracker.js +19 -19
- package/lib/watcher.js +35 -35
- package/lib/watcher.test.js +73 -0
- package/package.json +11 -6
- package/src/computed.test.ts +244 -0
- package/src/computed.ts +2 -1
- package/src/ref.test.ts +24 -0
- package/src/ref.ts +6 -2
- package/src/watcher.test.ts +62 -0
- package/types/computed.d.ts +29 -29
- package/types/computed.d.ts.map +1 -1
- package/types/computed.test.d.ts +2 -0
- package/types/computed.test.d.ts.map +1 -0
- package/types/dependency.d.ts +6 -6
- package/types/dependent.d.ts +6 -6
- package/types/index.d.ts +5 -5
- package/types/index.test.d.ts +1 -1
- package/types/ref.d.ts +8 -7
- package/types/ref.d.ts.map +1 -1
- package/types/ref.test.d.ts +2 -0
- package/types/ref.test.d.ts.map +1 -0
- package/types/tracker.d.ts +12 -12
- package/types/watcher.d.ts +14 -14
- package/types/watcher.test.d.ts +2 -0
- package/types/watcher.test.d.ts.map +1 -0
- package/lib/index.test.js +0 -310
- package/src/index.test.ts +0 -310
package/lib/computed.js
CHANGED
|
@@ -1,123 +1,124 @@
|
|
|
1
|
-
import Tracker from "./tracker.js";
|
|
2
|
-
var ComputedState;
|
|
3
|
-
(function (ComputedState) {
|
|
4
|
-
ComputedState[ComputedState["Invalid"] = 0] = "Invalid";
|
|
5
|
-
ComputedState[ComputedState["Valid"] = 1] = "Valid";
|
|
6
|
-
ComputedState[ComputedState["Uncertain"] = 2] = "Uncertain";
|
|
7
|
-
ComputedState[ComputedState["Computing"] = 3] = "Computing";
|
|
8
|
-
})(ComputedState || (ComputedState = {}));
|
|
9
|
-
;
|
|
10
|
-
class CircularDependencyError extends Error {
|
|
11
|
-
}
|
|
12
|
-
export default class Computed extends Tracker {
|
|
13
|
-
constructor(getter) {
|
|
14
|
-
super();
|
|
15
|
-
this.state = ComputedState.Invalid;
|
|
16
|
-
this.dependencies = new Map();
|
|
17
|
-
this.trackDependency = this.innerTrackDependency.bind(this);
|
|
18
|
-
this.getter = getter;
|
|
19
|
-
this.prepareComputePromise();
|
|
20
|
-
}
|
|
21
|
-
prepareComputePromise() {
|
|
22
|
-
this.computePromise = new Promise((resolve, reject) => {
|
|
23
|
-
this.computePromiseActions = {
|
|
24
|
-
resolve,
|
|
25
|
-
reject
|
|
26
|
-
};
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
get value() {
|
|
30
|
-
if (this.state === ComputedState.Invalid || this.state === ComputedState.Uncertain) {
|
|
31
|
-
return this.compute();
|
|
32
|
-
}
|
|
33
|
-
return this._value;
|
|
34
|
-
}
|
|
35
|
-
compute() {
|
|
36
|
-
if (this.state === ComputedState.Uncertain) {
|
|
37
|
-
for (const dependency of this.dependencies.keys()) {
|
|
38
|
-
this.dependencies.set(dependency, true);
|
|
39
|
-
}
|
|
40
|
-
if ([...this.dependencies.keys()].every(d => {
|
|
41
|
-
d.value;
|
|
42
|
-
return !this.dependencies.get(d);
|
|
43
|
-
})) {
|
|
44
|
-
this.finalizeComputing();
|
|
45
|
-
this.validateDependents();
|
|
46
|
-
return this._value;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
this.state = ComputedState.Computing;
|
|
50
|
-
this.clearDependencies();
|
|
51
|
-
const lastValue = this._value;
|
|
52
|
-
this._value = this.getter(this.trackDependency);
|
|
53
|
-
if (this._value instanceof Promise) {
|
|
54
|
-
const computeAttemptPromise = this._value
|
|
55
|
-
.then(result => this.handlePromiseThen(computeAttemptPromise, result))
|
|
56
|
-
.catch(error => this.handlePromiseCatch(computeAttemptPromise, error));
|
|
57
|
-
this.lastComputeAttemptPromise = computeAttemptPromise;
|
|
58
|
-
this._value = this.computePromise;
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
this.handlePromiseThen(this.lastComputeAttemptPromise, this._value);
|
|
62
|
-
if (lastValue === this._value) {
|
|
63
|
-
this.validateDependents();
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return this._value;
|
|
67
|
-
}
|
|
68
|
-
clearDependencies() {
|
|
69
|
-
for (const dependency of this.dependencies.keys()) {
|
|
70
|
-
dependency.removeDependent(this);
|
|
71
|
-
}
|
|
72
|
-
this.dependencies.clear();
|
|
73
|
-
}
|
|
74
|
-
handlePromiseThen(computeAttemptPromise, result) {
|
|
75
|
-
if (this.lastComputeAttemptPromise === computeAttemptPromise) {
|
|
76
|
-
this.computePromiseActions.resolve(result);
|
|
77
|
-
this.finalizeComputing();
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
handlePromiseCatch(computeAttemptPromise, error) {
|
|
81
|
-
if (this.lastComputeAttemptPromise === computeAttemptPromise) {
|
|
82
|
-
this.computePromiseActions.reject(error);
|
|
83
|
-
this.finalizeComputing();
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
innerTrackDependency(dependency) {
|
|
87
|
-
if (this.dependents.has(dependency)) {
|
|
88
|
-
throw new CircularDependencyError();
|
|
89
|
-
}
|
|
90
|
-
this.dependencies.set(dependency, true);
|
|
91
|
-
dependency.addDependent(this);
|
|
92
|
-
return dependency.value;
|
|
93
|
-
}
|
|
94
|
-
finalizeComputing() {
|
|
95
|
-
this.state = ComputedState.Valid;
|
|
96
|
-
this.lastComputeAttemptPromise = undefined;
|
|
97
|
-
this.prepareComputePromise();
|
|
98
|
-
}
|
|
99
|
-
invalidate() {
|
|
100
|
-
if (this.state === ComputedState.Computing) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
this.
|
|
119
|
-
this.
|
|
120
|
-
this.
|
|
121
|
-
this.
|
|
122
|
-
|
|
123
|
-
}
|
|
1
|
+
import Tracker from "./tracker.js";
|
|
2
|
+
var ComputedState;
|
|
3
|
+
(function (ComputedState) {
|
|
4
|
+
ComputedState[ComputedState["Invalid"] = 0] = "Invalid";
|
|
5
|
+
ComputedState[ComputedState["Valid"] = 1] = "Valid";
|
|
6
|
+
ComputedState[ComputedState["Uncertain"] = 2] = "Uncertain";
|
|
7
|
+
ComputedState[ComputedState["Computing"] = 3] = "Computing";
|
|
8
|
+
})(ComputedState || (ComputedState = {}));
|
|
9
|
+
;
|
|
10
|
+
class CircularDependencyError extends Error {
|
|
11
|
+
}
|
|
12
|
+
export default class Computed extends Tracker {
|
|
13
|
+
constructor(getter) {
|
|
14
|
+
super();
|
|
15
|
+
this.state = ComputedState.Invalid;
|
|
16
|
+
this.dependencies = new Map();
|
|
17
|
+
this.trackDependency = this.innerTrackDependency.bind(this);
|
|
18
|
+
this.getter = getter;
|
|
19
|
+
this.prepareComputePromise();
|
|
20
|
+
}
|
|
21
|
+
prepareComputePromise() {
|
|
22
|
+
this.computePromise = new Promise((resolve, reject) => {
|
|
23
|
+
this.computePromiseActions = {
|
|
24
|
+
resolve,
|
|
25
|
+
reject
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
get value() {
|
|
30
|
+
if (this.state === ComputedState.Invalid || this.state === ComputedState.Uncertain) {
|
|
31
|
+
return this.compute();
|
|
32
|
+
}
|
|
33
|
+
return this._value;
|
|
34
|
+
}
|
|
35
|
+
compute() {
|
|
36
|
+
if (this.state === ComputedState.Uncertain) {
|
|
37
|
+
for (const dependency of this.dependencies.keys()) {
|
|
38
|
+
this.dependencies.set(dependency, true);
|
|
39
|
+
}
|
|
40
|
+
if ([...this.dependencies.keys()].every(d => {
|
|
41
|
+
d.value;
|
|
42
|
+
return !this.dependencies.get(d);
|
|
43
|
+
})) {
|
|
44
|
+
this.finalizeComputing();
|
|
45
|
+
this.validateDependents();
|
|
46
|
+
return this._value;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
this.state = ComputedState.Computing;
|
|
50
|
+
this.clearDependencies();
|
|
51
|
+
const lastValue = this._value;
|
|
52
|
+
this._value = this.getter(this.trackDependency);
|
|
53
|
+
if (this._value instanceof Promise) {
|
|
54
|
+
const computeAttemptPromise = this._value
|
|
55
|
+
.then(result => this.handlePromiseThen(computeAttemptPromise, result))
|
|
56
|
+
.catch(error => this.handlePromiseCatch(computeAttemptPromise, error));
|
|
57
|
+
this.lastComputeAttemptPromise = computeAttemptPromise;
|
|
58
|
+
this._value = this.computePromise;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
this.handlePromiseThen(this.lastComputeAttemptPromise, this._value);
|
|
62
|
+
if (lastValue === this._value) {
|
|
63
|
+
this.validateDependents();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return this._value;
|
|
67
|
+
}
|
|
68
|
+
clearDependencies() {
|
|
69
|
+
for (const dependency of this.dependencies.keys()) {
|
|
70
|
+
dependency.removeDependent(this);
|
|
71
|
+
}
|
|
72
|
+
this.dependencies.clear();
|
|
73
|
+
}
|
|
74
|
+
handlePromiseThen(computeAttemptPromise, result) {
|
|
75
|
+
if (this.lastComputeAttemptPromise === computeAttemptPromise) {
|
|
76
|
+
this.computePromiseActions.resolve(result);
|
|
77
|
+
this.finalizeComputing();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
handlePromiseCatch(computeAttemptPromise, error) {
|
|
81
|
+
if (this.lastComputeAttemptPromise === computeAttemptPromise) {
|
|
82
|
+
this.computePromiseActions.reject(error);
|
|
83
|
+
this.finalizeComputing();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
innerTrackDependency(dependency) {
|
|
87
|
+
if (this.dependents.has(dependency)) {
|
|
88
|
+
throw new CircularDependencyError();
|
|
89
|
+
}
|
|
90
|
+
this.dependencies.set(dependency, true);
|
|
91
|
+
dependency.addDependent(this);
|
|
92
|
+
return dependency.value;
|
|
93
|
+
}
|
|
94
|
+
finalizeComputing() {
|
|
95
|
+
this.state = ComputedState.Valid;
|
|
96
|
+
this.lastComputeAttemptPromise = undefined;
|
|
97
|
+
this.prepareComputePromise();
|
|
98
|
+
}
|
|
99
|
+
invalidate() {
|
|
100
|
+
if (this.state === ComputedState.Computing) {
|
|
101
|
+
this.lastComputeAttemptPromise = undefined; // prevent finalizeComputing if it is in the event loop already
|
|
102
|
+
Promise.resolve().then(this.compute.bind(this));
|
|
103
|
+
}
|
|
104
|
+
else if (this.state === ComputedState.Valid) {
|
|
105
|
+
this.state = ComputedState.Uncertain;
|
|
106
|
+
super.invalidate();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
validate(dependency) {
|
|
110
|
+
this.dependencies.set(dependency, false);
|
|
111
|
+
}
|
|
112
|
+
validateDependents() {
|
|
113
|
+
for (const dependent of this.dependents.keys()) {
|
|
114
|
+
dependent.validate(this);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
dispose() {
|
|
118
|
+
this.clearDependencies();
|
|
119
|
+
this.state = ComputedState.Invalid;
|
|
120
|
+
this._value = undefined;
|
|
121
|
+
this.lastComputeAttemptPromise = undefined;
|
|
122
|
+
this.prepareComputePromise();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
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
|
+
import 'mocha';
|
|
11
|
+
import assert from 'assert';
|
|
12
|
+
import { Computed, Ref } from './index.js';
|
|
13
|
+
describe('computed', function () {
|
|
14
|
+
it('lazy compute', function () {
|
|
15
|
+
let gate = false;
|
|
16
|
+
const a = new Computed(() => {
|
|
17
|
+
gate = true;
|
|
18
|
+
return 5;
|
|
19
|
+
});
|
|
20
|
+
assert.strictEqual(gate, false);
|
|
21
|
+
assert.strictEqual(a.value, 5);
|
|
22
|
+
assert.strictEqual(gate, true);
|
|
23
|
+
});
|
|
24
|
+
it('cache', function () {
|
|
25
|
+
let gate = false;
|
|
26
|
+
const a = new Computed(() => {
|
|
27
|
+
gate = true;
|
|
28
|
+
return 5;
|
|
29
|
+
});
|
|
30
|
+
assert.strictEqual(a.value, 5);
|
|
31
|
+
gate = false;
|
|
32
|
+
assert.strictEqual(a.value, 5);
|
|
33
|
+
assert.strictEqual(gate, false);
|
|
34
|
+
});
|
|
35
|
+
it('invalidate dependents', function () {
|
|
36
|
+
const a = new Ref(5);
|
|
37
|
+
const b = new Computed((value) => {
|
|
38
|
+
return value(a) + 4;
|
|
39
|
+
});
|
|
40
|
+
assert.strictEqual(b.value, 9);
|
|
41
|
+
a.value = 6;
|
|
42
|
+
assert.strictEqual(b.value, 10);
|
|
43
|
+
});
|
|
44
|
+
it('dependents up-to-date', function () {
|
|
45
|
+
const a = new Ref(5);
|
|
46
|
+
const b = new Ref(10);
|
|
47
|
+
let gate;
|
|
48
|
+
const c = new Computed((value) => {
|
|
49
|
+
gate = true;
|
|
50
|
+
return value(a) < 10 ? value(b) : 0;
|
|
51
|
+
});
|
|
52
|
+
assert.strictEqual(c.value, 10);
|
|
53
|
+
a.value = 15;
|
|
54
|
+
assert.strictEqual(c.value, 0);
|
|
55
|
+
b.value = 15;
|
|
56
|
+
gate = false;
|
|
57
|
+
assert.strictEqual(c.value, 0);
|
|
58
|
+
assert.strictEqual(gate, false);
|
|
59
|
+
});
|
|
60
|
+
it('detect circular dependency', function () {
|
|
61
|
+
// @ts-expect-error
|
|
62
|
+
const a = new Computed((value) => {
|
|
63
|
+
return value(b);
|
|
64
|
+
});
|
|
65
|
+
// @ts-expect-error
|
|
66
|
+
const b = new Computed((value) => {
|
|
67
|
+
return value(a);
|
|
68
|
+
});
|
|
69
|
+
assert.throws(() => a.value);
|
|
70
|
+
});
|
|
71
|
+
xit('detect circular deeper dependency', function () {
|
|
72
|
+
// do not support for better performance
|
|
73
|
+
assert.fail('not implemented');
|
|
74
|
+
});
|
|
75
|
+
it('throw error', function () {
|
|
76
|
+
const a = new Computed(() => {
|
|
77
|
+
throw new Error();
|
|
78
|
+
});
|
|
79
|
+
assert.throws(() => a.value);
|
|
80
|
+
});
|
|
81
|
+
it('ignore same ref value', function () {
|
|
82
|
+
let gate = 0;
|
|
83
|
+
const a = new Ref(5);
|
|
84
|
+
const b = new Computed((value) => {
|
|
85
|
+
gate++;
|
|
86
|
+
return value(a);
|
|
87
|
+
});
|
|
88
|
+
assert.strictEqual(b.value, 5);
|
|
89
|
+
a.value = 5;
|
|
90
|
+
assert.strictEqual(b.value, 5);
|
|
91
|
+
assert.strictEqual(gate, 1);
|
|
92
|
+
});
|
|
93
|
+
it('ignore same computed value', function () {
|
|
94
|
+
let gate = 0;
|
|
95
|
+
const a = new Ref(5);
|
|
96
|
+
const b = new Computed((value) => {
|
|
97
|
+
return value(a) % 2;
|
|
98
|
+
});
|
|
99
|
+
const c = new Computed((value) => {
|
|
100
|
+
gate++;
|
|
101
|
+
return value(b) + 5;
|
|
102
|
+
});
|
|
103
|
+
assert.strictEqual(c.value, 6);
|
|
104
|
+
a.value = 7;
|
|
105
|
+
assert.strictEqual(c.value, 6);
|
|
106
|
+
assert.strictEqual(gate, 1);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
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);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
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);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
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);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
});
|
package/lib/dependency.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {};
|
|
1
|
+
export {};
|
package/lib/dependent.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {};
|
|
1
|
+
export {};
|
package/lib/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { default as Computed } from './computed.js';
|
|
2
|
-
export { default as Ref } from './ref.js';
|
|
3
|
-
export { default as Watcher } from './watcher.js';
|
|
1
|
+
export { default as Computed } from './computed.js';
|
|
2
|
+
export { default as Ref } from './ref.js';
|
|
3
|
+
export { default as Watcher } from './watcher.js';
|
package/lib/ref.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import Tracker from "./tracker.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
import Tracker from "./tracker.js";
|
|
2
|
+
const defaultIsEqual = (v1, v2) => v1 === v2;
|
|
3
|
+
export default class Ref extends Tracker {
|
|
4
|
+
constructor(_value, isEqual = (defaultIsEqual)) {
|
|
5
|
+
super();
|
|
6
|
+
this._value = _value;
|
|
7
|
+
this.isEqual = isEqual;
|
|
8
|
+
}
|
|
9
|
+
set value(_value) {
|
|
10
|
+
const lastValue = this._value;
|
|
11
|
+
this._value = _value;
|
|
12
|
+
if (!this.isEqual(lastValue, _value)) {
|
|
13
|
+
this.invalidate();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
get value() {
|
|
17
|
+
return super.value;
|
|
18
|
+
}
|
|
19
|
+
}
|
package/lib/ref.test.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import 'mocha';
|
|
2
|
+
import assert from 'assert';
|
|
3
|
+
import { Ref, Watcher } from './index.js';
|
|
4
|
+
describe('ref', function () {
|
|
5
|
+
it('getter', function () {
|
|
6
|
+
const a = new Ref(5);
|
|
7
|
+
assert.strictEqual(a.value, 5);
|
|
8
|
+
});
|
|
9
|
+
it('setter', function () {
|
|
10
|
+
const a = new Ref(5);
|
|
11
|
+
a.value = 4;
|
|
12
|
+
assert.strictEqual(a.value, 4);
|
|
13
|
+
});
|
|
14
|
+
it('setter isEqual', function () {
|
|
15
|
+
const a = new Ref(5, () => true);
|
|
16
|
+
new Watcher(a, () => {
|
|
17
|
+
assert.fail();
|
|
18
|
+
}, false);
|
|
19
|
+
a.value = 4;
|
|
20
|
+
});
|
|
21
|
+
});
|
package/lib/tracker.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
export default class Tracker {
|
|
2
|
-
constructor() {
|
|
3
|
-
this.dependents = new Set();
|
|
4
|
-
}
|
|
5
|
-
addDependent(dependent) {
|
|
6
|
-
this.dependents.add(dependent);
|
|
7
|
-
}
|
|
8
|
-
removeDependent(dependent) {
|
|
9
|
-
this.dependents.delete(dependent);
|
|
10
|
-
}
|
|
11
|
-
invalidate() {
|
|
12
|
-
for (const dependent of [...this.dependents.keys()]) {
|
|
13
|
-
dependent.invalidate();
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
get value() {
|
|
17
|
-
return this._value;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
1
|
+
export default class Tracker {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.dependents = new Set();
|
|
4
|
+
}
|
|
5
|
+
addDependent(dependent) {
|
|
6
|
+
this.dependents.add(dependent);
|
|
7
|
+
}
|
|
8
|
+
removeDependent(dependent) {
|
|
9
|
+
this.dependents.delete(dependent);
|
|
10
|
+
}
|
|
11
|
+
invalidate() {
|
|
12
|
+
for (const dependent of [...this.dependents.keys()]) {
|
|
13
|
+
dependent.invalidate();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
get value() {
|
|
17
|
+
return this._value;
|
|
18
|
+
}
|
|
19
|
+
}
|
package/lib/watcher.js
CHANGED
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
import Tracker from "./tracker.js";
|
|
2
|
-
var WatchState;
|
|
3
|
-
(function (WatchState) {
|
|
4
|
-
WatchState[WatchState["Uncertain"] = 0] = "Uncertain";
|
|
5
|
-
WatchState[WatchState["Valid"] = 1] = "Valid";
|
|
6
|
-
})(WatchState || (WatchState = {}));
|
|
7
|
-
;
|
|
8
|
-
export default class Watcher extends Tracker {
|
|
9
|
-
constructor(dependency, onChange, immediate = true) {
|
|
10
|
-
super();
|
|
11
|
-
this.state = WatchState.Valid;
|
|
12
|
-
this.onChange = onChange;
|
|
13
|
-
this.dependency = dependency;
|
|
14
|
-
dependency.addDependent(this);
|
|
15
|
-
this._value = dependency.value;
|
|
16
|
-
if (immediate) {
|
|
17
|
-
onChange(this._value);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
invalidate() {
|
|
21
|
-
this.state = WatchState.Uncertain;
|
|
22
|
-
const oldValue = this._value;
|
|
23
|
-
this._value = this.dependency.value;
|
|
24
|
-
if (this.state === WatchState.Uncertain) {
|
|
25
|
-
this.onChange(this._value, oldValue);
|
|
26
|
-
this.state = WatchState.Valid;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
validate() {
|
|
30
|
-
this.state = WatchState.Valid;
|
|
31
|
-
}
|
|
32
|
-
dispose() {
|
|
33
|
-
this.dependency.removeDependent(this);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
1
|
+
import Tracker from "./tracker.js";
|
|
2
|
+
var WatchState;
|
|
3
|
+
(function (WatchState) {
|
|
4
|
+
WatchState[WatchState["Uncertain"] = 0] = "Uncertain";
|
|
5
|
+
WatchState[WatchState["Valid"] = 1] = "Valid";
|
|
6
|
+
})(WatchState || (WatchState = {}));
|
|
7
|
+
;
|
|
8
|
+
export default class Watcher extends Tracker {
|
|
9
|
+
constructor(dependency, onChange, immediate = true) {
|
|
10
|
+
super();
|
|
11
|
+
this.state = WatchState.Valid;
|
|
12
|
+
this.onChange = onChange;
|
|
13
|
+
this.dependency = dependency;
|
|
14
|
+
dependency.addDependent(this);
|
|
15
|
+
this._value = dependency.value;
|
|
16
|
+
if (immediate) {
|
|
17
|
+
onChange(this._value);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
invalidate() {
|
|
21
|
+
this.state = WatchState.Uncertain;
|
|
22
|
+
const oldValue = this._value;
|
|
23
|
+
this._value = this.dependency.value;
|
|
24
|
+
if (this.state === WatchState.Uncertain) {
|
|
25
|
+
this.onChange(this._value, oldValue);
|
|
26
|
+
this.state = WatchState.Valid;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
validate() {
|
|
30
|
+
this.state = WatchState.Valid;
|
|
31
|
+
}
|
|
32
|
+
dispose() {
|
|
33
|
+
this.dependency.removeDependent(this);
|
|
34
|
+
}
|
|
35
|
+
}
|