async-reactivity 1.0.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/lib/computed.js +129 -0
- package/lib/dependency.js +2 -0
- package/lib/dependent.js +2 -0
- package/lib/index.js +12 -0
- package/lib/index.test.js +335 -0
- package/lib/ref.js +23 -0
- package/lib/tracker.js +22 -0
- package/lib/watcher.js +41 -0
- package/package.json +24 -0
- package/src/computed.ts +148 -0
- package/src/dependency.ts +7 -0
- package/src/dependent.ts +7 -0
- package/src/index.test.ts +310 -0
- package/src/index.ts +5 -0
- package/src/ref.ts +22 -0
- package/src/tracker.ts +27 -0
- package/src/tsconfig.json +95 -0
- package/src/watcher.ts +47 -0
- package/types/computed.d.ts +30 -0
- package/types/computed.d.ts.map +1 -0
- package/types/dependency.d.ts +7 -0
- package/types/dependency.d.ts.map +1 -0
- package/types/dependent.d.ts +7 -0
- package/types/dependent.d.ts.map +1 -0
- package/types/index.d.ts +6 -0
- package/types/index.d.ts.map +1 -0
- package/types/index.test.d.ts +2 -0
- package/types/index.test.d.ts.map +1 -0
- package/types/ref.d.ts +8 -0
- package/types/ref.d.ts.map +1 -0
- package/types/tracker.d.ts +13 -0
- package/types/tracker.d.ts.map +1 -0
- package/types/watcher.d.ts +15 -0
- package/types/watcher.d.ts.map +1 -0
package/lib/computed.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const tracker_1 = __importDefault(require("./tracker"));
|
|
7
|
+
var ComputedState;
|
|
8
|
+
(function (ComputedState) {
|
|
9
|
+
ComputedState[ComputedState["Invalid"] = 0] = "Invalid";
|
|
10
|
+
ComputedState[ComputedState["Valid"] = 1] = "Valid";
|
|
11
|
+
ComputedState[ComputedState["Uncertain"] = 2] = "Uncertain";
|
|
12
|
+
ComputedState[ComputedState["Computing"] = 3] = "Computing";
|
|
13
|
+
})(ComputedState || (ComputedState = {}));
|
|
14
|
+
;
|
|
15
|
+
class CircularDependencyError extends Error {
|
|
16
|
+
}
|
|
17
|
+
class Computed extends tracker_1.default {
|
|
18
|
+
constructor(getter) {
|
|
19
|
+
super();
|
|
20
|
+
this.state = ComputedState.Invalid;
|
|
21
|
+
this.dependencies = new Map();
|
|
22
|
+
this.trackDependency = this.innerTrackDependency.bind(this);
|
|
23
|
+
this.getter = getter;
|
|
24
|
+
this.prepareComputePromise();
|
|
25
|
+
}
|
|
26
|
+
prepareComputePromise() {
|
|
27
|
+
this.computePromise = new Promise((resolve, reject) => {
|
|
28
|
+
this.computePromiseActions = {
|
|
29
|
+
resolve,
|
|
30
|
+
reject
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
get value() {
|
|
35
|
+
if (this.state === ComputedState.Invalid || this.state === ComputedState.Uncertain) {
|
|
36
|
+
return this.compute();
|
|
37
|
+
}
|
|
38
|
+
return this._value;
|
|
39
|
+
}
|
|
40
|
+
compute() {
|
|
41
|
+
if (this.state === ComputedState.Uncertain) {
|
|
42
|
+
for (const dependency of this.dependencies.keys()) {
|
|
43
|
+
this.dependencies.set(dependency, true);
|
|
44
|
+
}
|
|
45
|
+
if ([...this.dependencies.keys()].every(d => {
|
|
46
|
+
d.value;
|
|
47
|
+
return !this.dependencies.get(d);
|
|
48
|
+
})) {
|
|
49
|
+
this.finalizeComputing();
|
|
50
|
+
this.validateDependents();
|
|
51
|
+
return this._value;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
this.state = ComputedState.Computing;
|
|
55
|
+
this.clearDependencies();
|
|
56
|
+
const lastValue = this._value;
|
|
57
|
+
this._value = this.getter(this.trackDependency);
|
|
58
|
+
if (this._value instanceof Promise) {
|
|
59
|
+
const computeAttemptPromise = this._value
|
|
60
|
+
.then(result => this.handlePromiseThen(computeAttemptPromise, result))
|
|
61
|
+
.catch(error => this.handlePromiseCatch(computeAttemptPromise, error));
|
|
62
|
+
this.lastComputeAttemptPromise = computeAttemptPromise;
|
|
63
|
+
this._value = this.computePromise;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
this.handlePromiseThen(this.lastComputeAttemptPromise, this._value);
|
|
67
|
+
if (lastValue === this._value) {
|
|
68
|
+
this.validateDependents();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return this._value;
|
|
72
|
+
}
|
|
73
|
+
clearDependencies() {
|
|
74
|
+
for (const dependency of this.dependencies.keys()) {
|
|
75
|
+
dependency.removeDependent(this);
|
|
76
|
+
}
|
|
77
|
+
this.dependencies.clear();
|
|
78
|
+
}
|
|
79
|
+
handlePromiseThen(computeAttemptPromise, result) {
|
|
80
|
+
if (this.lastComputeAttemptPromise === computeAttemptPromise) {
|
|
81
|
+
this.computePromiseActions.resolve(result);
|
|
82
|
+
this.finalizeComputing();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
handlePromiseCatch(computeAttemptPromise, error) {
|
|
86
|
+
if (this.lastComputeAttemptPromise === computeAttemptPromise) {
|
|
87
|
+
this.computePromiseActions.reject(error);
|
|
88
|
+
this.finalizeComputing();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
innerTrackDependency(dependency) {
|
|
92
|
+
if (this.dependents.has(dependency)) {
|
|
93
|
+
throw new CircularDependencyError();
|
|
94
|
+
}
|
|
95
|
+
this.dependencies.set(dependency, true);
|
|
96
|
+
dependency.addDependent(this);
|
|
97
|
+
return dependency.value;
|
|
98
|
+
}
|
|
99
|
+
finalizeComputing() {
|
|
100
|
+
this.state = ComputedState.Valid;
|
|
101
|
+
this.lastComputeAttemptPromise = undefined;
|
|
102
|
+
this.prepareComputePromise();
|
|
103
|
+
}
|
|
104
|
+
invalidate() {
|
|
105
|
+
if (this.state === ComputedState.Computing) {
|
|
106
|
+
setImmediate(this.compute.bind(this));
|
|
107
|
+
}
|
|
108
|
+
if (this.state !== ComputedState.Uncertain) {
|
|
109
|
+
this.state = ComputedState.Uncertain;
|
|
110
|
+
super.invalidate();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
validate(dependency) {
|
|
114
|
+
this.dependencies.set(dependency, false);
|
|
115
|
+
}
|
|
116
|
+
validateDependents() {
|
|
117
|
+
for (const dependent of this.dependents.keys()) {
|
|
118
|
+
dependent.validate(this);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
dispose() {
|
|
122
|
+
this.clearDependencies();
|
|
123
|
+
this.state = ComputedState.Invalid;
|
|
124
|
+
this._value = undefined;
|
|
125
|
+
this.lastComputeAttemptPromise = undefined;
|
|
126
|
+
this.prepareComputePromise();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
exports.default = Computed;
|
package/lib/dependent.js
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Watcher = exports.Ref = exports.Computed = void 0;
|
|
7
|
+
var computed_1 = require("./computed");
|
|
8
|
+
Object.defineProperty(exports, "Computed", { enumerable: true, get: function () { return __importDefault(computed_1).default; } });
|
|
9
|
+
var ref_1 = require("./ref");
|
|
10
|
+
Object.defineProperty(exports, "Ref", { enumerable: true, get: function () { return __importDefault(ref_1).default; } });
|
|
11
|
+
var watcher_1 = require("./watcher");
|
|
12
|
+
Object.defineProperty(exports, "Watcher", { enumerable: true, get: function () { return __importDefault(watcher_1).default; } });
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
+
const _1 = require("./");
|
|
36
|
+
const assert = __importStar(require("assert"));
|
|
37
|
+
describe('async reactivity', function () {
|
|
38
|
+
describe('ref', function () {
|
|
39
|
+
it('getter', function () {
|
|
40
|
+
const a = new _1.Ref(5);
|
|
41
|
+
assert.strictEqual(a.value, 5);
|
|
42
|
+
});
|
|
43
|
+
it('setter', function () {
|
|
44
|
+
const a = new _1.Ref(5);
|
|
45
|
+
a.value = 4;
|
|
46
|
+
assert.strictEqual(a.value, 4);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
describe('computed', function () {
|
|
50
|
+
it('lazy compute', function () {
|
|
51
|
+
let gate = false;
|
|
52
|
+
const a = new _1.Computed(() => {
|
|
53
|
+
gate = true;
|
|
54
|
+
return 5;
|
|
55
|
+
});
|
|
56
|
+
assert.strictEqual(gate, false);
|
|
57
|
+
assert.strictEqual(a.value, 5);
|
|
58
|
+
assert.strictEqual(gate, true);
|
|
59
|
+
});
|
|
60
|
+
it('cache', function () {
|
|
61
|
+
let gate = false;
|
|
62
|
+
const a = new _1.Computed(() => {
|
|
63
|
+
gate = true;
|
|
64
|
+
return 5;
|
|
65
|
+
});
|
|
66
|
+
assert.strictEqual(a.value, 5);
|
|
67
|
+
gate = false;
|
|
68
|
+
assert.strictEqual(a.value, 5);
|
|
69
|
+
assert.strictEqual(gate, false);
|
|
70
|
+
});
|
|
71
|
+
it('invalidate dependents', function () {
|
|
72
|
+
const a = new _1.Ref(5);
|
|
73
|
+
const b = new _1.Computed((value) => {
|
|
74
|
+
return value(a) + 4;
|
|
75
|
+
});
|
|
76
|
+
assert.strictEqual(b.value, 9);
|
|
77
|
+
a.value = 6;
|
|
78
|
+
assert.strictEqual(b.value, 10);
|
|
79
|
+
});
|
|
80
|
+
it('dependents up-to-date', function () {
|
|
81
|
+
const a = new _1.Ref(5);
|
|
82
|
+
const b = new _1.Ref(10);
|
|
83
|
+
let gate;
|
|
84
|
+
const c = new _1.Computed((value) => {
|
|
85
|
+
gate = true;
|
|
86
|
+
return value(a) < 10 ? value(b) : 0;
|
|
87
|
+
});
|
|
88
|
+
assert.strictEqual(c.value, 10);
|
|
89
|
+
a.value = 15;
|
|
90
|
+
assert.strictEqual(c.value, 0);
|
|
91
|
+
b.value = 15;
|
|
92
|
+
gate = false;
|
|
93
|
+
assert.strictEqual(c.value, 0);
|
|
94
|
+
assert.strictEqual(gate, false);
|
|
95
|
+
});
|
|
96
|
+
it('detect circular dependency', function () {
|
|
97
|
+
// @ts-expect-error
|
|
98
|
+
const a = new _1.Computed((value) => {
|
|
99
|
+
return value(b);
|
|
100
|
+
});
|
|
101
|
+
// @ts-expect-error
|
|
102
|
+
const b = new _1.Computed((value) => {
|
|
103
|
+
return value(a);
|
|
104
|
+
});
|
|
105
|
+
assert.throws(() => a.value);
|
|
106
|
+
});
|
|
107
|
+
xit('detect circular deeper dependency', function () {
|
|
108
|
+
// do not support for better performance
|
|
109
|
+
assert.fail('not implemented');
|
|
110
|
+
});
|
|
111
|
+
it('throw error', function () {
|
|
112
|
+
const a = new _1.Computed(() => {
|
|
113
|
+
throw new Error();
|
|
114
|
+
});
|
|
115
|
+
assert.throws(() => a.value);
|
|
116
|
+
});
|
|
117
|
+
it('ignore same ref value', function () {
|
|
118
|
+
let gate = 0;
|
|
119
|
+
const a = new _1.Ref(5);
|
|
120
|
+
const b = new _1.Computed((value) => {
|
|
121
|
+
gate++;
|
|
122
|
+
return value(a);
|
|
123
|
+
});
|
|
124
|
+
assert.strictEqual(b.value, 5);
|
|
125
|
+
a.value = 5;
|
|
126
|
+
assert.strictEqual(b.value, 5);
|
|
127
|
+
assert.strictEqual(gate, 1);
|
|
128
|
+
});
|
|
129
|
+
it('ignore same computed value', function () {
|
|
130
|
+
let gate = 0;
|
|
131
|
+
const a = new _1.Ref(5);
|
|
132
|
+
const b = new _1.Computed((value) => {
|
|
133
|
+
return value(a) % 2;
|
|
134
|
+
});
|
|
135
|
+
const c = new _1.Computed((value) => {
|
|
136
|
+
gate++;
|
|
137
|
+
return value(b) + 5;
|
|
138
|
+
});
|
|
139
|
+
assert.strictEqual(c.value, 6);
|
|
140
|
+
a.value = 7;
|
|
141
|
+
assert.strictEqual(c.value, 6);
|
|
142
|
+
assert.strictEqual(gate, 1);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
describe('async computed', function () {
|
|
146
|
+
it('getter', function () {
|
|
147
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
148
|
+
const a = new _1.Computed(() => __awaiter(this, void 0, void 0, function* () {
|
|
149
|
+
yield new Promise(resolve => setTimeout(resolve));
|
|
150
|
+
return 5;
|
|
151
|
+
}));
|
|
152
|
+
assert.strictEqual(yield a.value, 5);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
it('tracks async dependencies', function () {
|
|
156
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
157
|
+
const a = new _1.Ref(5);
|
|
158
|
+
const b = new _1.Computed((value) => __awaiter(this, void 0, void 0, function* () {
|
|
159
|
+
yield new Promise(resolve => setTimeout(resolve));
|
|
160
|
+
return value(a) + 5;
|
|
161
|
+
}));
|
|
162
|
+
assert.strictEqual(yield b.value, 10);
|
|
163
|
+
a.value = 6;
|
|
164
|
+
assert.strictEqual(yield b.value, 11);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
it('get value while computing', function () {
|
|
168
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
169
|
+
const a = new _1.Computed(() => __awaiter(this, void 0, void 0, function* () {
|
|
170
|
+
yield new Promise(resolve => setTimeout(resolve));
|
|
171
|
+
return 5;
|
|
172
|
+
}));
|
|
173
|
+
a.value;
|
|
174
|
+
assert.strictEqual(yield a.value, 5);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
it('detect circular dependency', function () {
|
|
178
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
179
|
+
// @ts-expect-error
|
|
180
|
+
const a = new _1.Computed((value) => __awaiter(this, void 0, void 0, function* () {
|
|
181
|
+
yield new Promise(resolve => setTimeout(resolve));
|
|
182
|
+
return value(b);
|
|
183
|
+
}));
|
|
184
|
+
// @ts-expect-error
|
|
185
|
+
const b = new _1.Computed((value) => __awaiter(this, void 0, void 0, function* () {
|
|
186
|
+
yield new Promise(resolve => setTimeout(resolve));
|
|
187
|
+
return value(a);
|
|
188
|
+
}));
|
|
189
|
+
assert.rejects(() => __awaiter(this, void 0, void 0, function* () { return yield a.value; }));
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
it('old dependency changed while computing', function () {
|
|
193
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
194
|
+
let gate = 0;
|
|
195
|
+
const a = new _1.Ref(5);
|
|
196
|
+
const b = new _1.Computed((value) => __awaiter(this, void 0, void 0, function* () {
|
|
197
|
+
gate++;
|
|
198
|
+
yield new Promise(resolve => setTimeout(resolve));
|
|
199
|
+
return value(a) + 2;
|
|
200
|
+
}));
|
|
201
|
+
assert.strictEqual(yield b.value, 7);
|
|
202
|
+
b.invalidate();
|
|
203
|
+
const promise = b.value;
|
|
204
|
+
a.value = 6;
|
|
205
|
+
assert.strictEqual(yield promise, 8);
|
|
206
|
+
assert.strictEqual(gate, 2);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
it('new dependency changed while computing', function () {
|
|
210
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
211
|
+
let gate = 0;
|
|
212
|
+
const a = new _1.Ref(5);
|
|
213
|
+
const b = new _1.Ref(10);
|
|
214
|
+
const c = new _1.Computed((value) => __awaiter(this, void 0, void 0, function* () {
|
|
215
|
+
gate++;
|
|
216
|
+
yield new Promise(resolve => setTimeout(resolve, 50));
|
|
217
|
+
let sum = value(a);
|
|
218
|
+
yield new Promise(resolve => setTimeout(resolve, 50));
|
|
219
|
+
sum += value(b);
|
|
220
|
+
return sum;
|
|
221
|
+
}));
|
|
222
|
+
assert.strictEqual(yield c.value, 15);
|
|
223
|
+
c.invalidate();
|
|
224
|
+
const promise = c.value;
|
|
225
|
+
yield new Promise(resolve => setTimeout(resolve, 60));
|
|
226
|
+
a.value = 10;
|
|
227
|
+
assert.strictEqual(yield promise, 20);
|
|
228
|
+
assert.strictEqual(gate, 3);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
it('fallback to primitive while computing', function () {
|
|
232
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
233
|
+
const a = new _1.Ref(5);
|
|
234
|
+
const b = new _1.Ref(10);
|
|
235
|
+
const c = new _1.Computed((value) => __awaiter(this, void 0, void 0, function* () {
|
|
236
|
+
if (value(a) < 10) {
|
|
237
|
+
yield new Promise(resolve => setTimeout(resolve, 50));
|
|
238
|
+
return value(b) + 5;
|
|
239
|
+
}
|
|
240
|
+
return 2;
|
|
241
|
+
}));
|
|
242
|
+
const promise = c.value;
|
|
243
|
+
yield new Promise(resolve => setTimeout(resolve, 20));
|
|
244
|
+
a.value = 10;
|
|
245
|
+
assert.strictEqual(yield promise, 2);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
it('throw error', function () {
|
|
249
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
250
|
+
const a = new _1.Computed(() => __awaiter(this, void 0, void 0, function* () {
|
|
251
|
+
yield new Promise(resolve => setTimeout(resolve));
|
|
252
|
+
throw new Error();
|
|
253
|
+
}));
|
|
254
|
+
assert.rejects(() => a.value);
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
it('dispose computed', function () {
|
|
258
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
259
|
+
const a = new _1.Ref(5);
|
|
260
|
+
let gate = 0;
|
|
261
|
+
const b = new _1.Computed(value => {
|
|
262
|
+
gate++;
|
|
263
|
+
return value(a) + 2;
|
|
264
|
+
});
|
|
265
|
+
b.value;
|
|
266
|
+
assert.strictEqual(gate, 1);
|
|
267
|
+
b.dispose();
|
|
268
|
+
a.value = 6;
|
|
269
|
+
b.value;
|
|
270
|
+
assert.strictEqual(gate, 1);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
describe('watcher', function () {
|
|
275
|
+
it('sync', function () {
|
|
276
|
+
const a = new _1.Ref(5);
|
|
277
|
+
new _1.Watcher(a, (newValue, oldValue) => {
|
|
278
|
+
assert.strictEqual(oldValue, 5);
|
|
279
|
+
assert.strictEqual(newValue, 6);
|
|
280
|
+
}, false);
|
|
281
|
+
a.value = 6;
|
|
282
|
+
});
|
|
283
|
+
it('async', function () {
|
|
284
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
285
|
+
const a = new _1.Computed(() => __awaiter(this, void 0, void 0, function* () {
|
|
286
|
+
yield new Promise(resolve => setTimeout(resolve));
|
|
287
|
+
return 10;
|
|
288
|
+
}));
|
|
289
|
+
const result = yield new Promise(resolve => new _1.Watcher(a, resolve));
|
|
290
|
+
assert.strictEqual(result, 10);
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
it('sync cancel', function () {
|
|
294
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
295
|
+
const a = new _1.Ref(5);
|
|
296
|
+
const b = new _1.Computed((value) => {
|
|
297
|
+
return value(a) % 2;
|
|
298
|
+
});
|
|
299
|
+
let gate = 0;
|
|
300
|
+
new _1.Watcher(b, () => {
|
|
301
|
+
gate++;
|
|
302
|
+
}, false);
|
|
303
|
+
a.value = 6;
|
|
304
|
+
assert.strictEqual(gate, 1);
|
|
305
|
+
a.value = 7;
|
|
306
|
+
a.value = 9;
|
|
307
|
+
a.value = 11;
|
|
308
|
+
assert.strictEqual(gate, 2);
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
it('async sync cancel', function () {
|
|
312
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
313
|
+
const a = new _1.Ref(5);
|
|
314
|
+
const b = new _1.Computed((value) => __awaiter(this, void 0, void 0, function* () {
|
|
315
|
+
new Promise(resolve => setTimeout(resolve));
|
|
316
|
+
return value(a) % 2;
|
|
317
|
+
}));
|
|
318
|
+
let gate = 0;
|
|
319
|
+
new _1.Watcher(b, () => {
|
|
320
|
+
gate++;
|
|
321
|
+
}, false);
|
|
322
|
+
yield new Promise(resolve => setTimeout(resolve, 10));
|
|
323
|
+
a.value = 6;
|
|
324
|
+
yield new Promise(resolve => setTimeout(resolve, 10));
|
|
325
|
+
assert.strictEqual(gate, 1);
|
|
326
|
+
a.value = 7;
|
|
327
|
+
yield new Promise(resolve => setTimeout(resolve, 10));
|
|
328
|
+
assert.strictEqual(gate, 2);
|
|
329
|
+
a.value = 9;
|
|
330
|
+
yield new Promise(resolve => setTimeout(resolve, 10));
|
|
331
|
+
assert.strictEqual(gate, 3);
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
});
|
package/lib/ref.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const tracker_1 = __importDefault(require("./tracker"));
|
|
7
|
+
class Ref extends tracker_1.default {
|
|
8
|
+
constructor(_value) {
|
|
9
|
+
super();
|
|
10
|
+
this._value = _value;
|
|
11
|
+
}
|
|
12
|
+
set value(_value) {
|
|
13
|
+
const lastValue = this._value;
|
|
14
|
+
this._value = _value;
|
|
15
|
+
if (lastValue !== _value) {
|
|
16
|
+
this.invalidate();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
get value() {
|
|
20
|
+
return super.value;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.default = Ref;
|
package/lib/tracker.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
class Tracker {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.dependents = new Set();
|
|
6
|
+
}
|
|
7
|
+
addDependent(dependent) {
|
|
8
|
+
this.dependents.add(dependent);
|
|
9
|
+
}
|
|
10
|
+
removeDependent(dependent) {
|
|
11
|
+
this.dependents.delete(dependent);
|
|
12
|
+
}
|
|
13
|
+
invalidate() {
|
|
14
|
+
for (const dependent of [...this.dependents.keys()]) {
|
|
15
|
+
dependent.invalidate();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
get value() {
|
|
19
|
+
return this._value;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.default = Tracker;
|
package/lib/watcher.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const tracker_1 = __importDefault(require("./tracker"));
|
|
7
|
+
var WatchState;
|
|
8
|
+
(function (WatchState) {
|
|
9
|
+
WatchState[WatchState["Uncertain"] = 0] = "Uncertain";
|
|
10
|
+
WatchState[WatchState["Valid"] = 1] = "Valid";
|
|
11
|
+
})(WatchState || (WatchState = {}));
|
|
12
|
+
;
|
|
13
|
+
class Watcher extends tracker_1.default {
|
|
14
|
+
constructor(dependency, onChange, immediate = true) {
|
|
15
|
+
super();
|
|
16
|
+
this.state = WatchState.Valid;
|
|
17
|
+
this.onChange = onChange;
|
|
18
|
+
this.dependency = dependency;
|
|
19
|
+
dependency.addDependent(this);
|
|
20
|
+
this._value = dependency.value;
|
|
21
|
+
if (immediate) {
|
|
22
|
+
onChange(this._value);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
invalidate() {
|
|
26
|
+
this.state = WatchState.Uncertain;
|
|
27
|
+
const oldValue = this._value;
|
|
28
|
+
this._value = this.dependency.value;
|
|
29
|
+
if (this.state === WatchState.Uncertain) {
|
|
30
|
+
this.onChange(this._value, oldValue);
|
|
31
|
+
this.state = WatchState.Valid;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
validate() {
|
|
35
|
+
this.state = WatchState.Valid;
|
|
36
|
+
}
|
|
37
|
+
dispose() {
|
|
38
|
+
this.dependency.removeDependent(this);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.default = Watcher;
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "async-reactivity",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "lib/index.ts",
|
|
6
|
+
"types": "lib/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"lib",
|
|
9
|
+
"src",
|
|
10
|
+
"types"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"dev": "npx tsc -w -p src",
|
|
14
|
+
"test-dev": "mocha --watch \"./{,!(node_modules)/**/}*.test.js\""
|
|
15
|
+
},
|
|
16
|
+
"author": "Donatas Lučiūnas",
|
|
17
|
+
"license": "ISC",
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/mocha": "^9.1.1",
|
|
20
|
+
"@types/node": "^17.0.41",
|
|
21
|
+
"mocha": "^10.0.0",
|
|
22
|
+
"typescript": "^4.7.3"
|
|
23
|
+
}
|
|
24
|
+
}
|