@fbltd/async 1.0.23 → 1.0.25
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 +29 -0
- package/dist/bin/dependency/dependency.js +45 -18
- package/dist/bin/dependency/global.js +4 -2
- package/dist/bin/dependency/stream-utils/get.stream.js +4 -0
- package/dist/bin/dependency/stream-utils/once.stream.js +2 -1
- package/dist/bin/dependency/stream-utils/reaction.js +23 -9
- package/dist/types/dependency/contracts.d.ts +13 -0
- package/dist/types/dependency/dependency.d.ts +17 -2
- package/dist/types/dependency/global.d.ts +2 -2
- package/dist/types/dependency/stream-utils/get.stream.d.ts +3 -0
- package/dist/types/dependency/stream-utils/reaction.d.ts +4 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -72,6 +72,35 @@ setInterval(() => {
|
|
|
72
72
|
##### raceStream
|
|
73
73
|
##### next
|
|
74
74
|
|
|
75
|
+
##### reaction
|
|
76
|
+
The most powerful util among all stream utils.
|
|
77
|
+
Function is watching only for actual dependencies and does not react
|
|
78
|
+
for dependencies that do not affect result value:
|
|
79
|
+
```typescript
|
|
80
|
+
let isDep1Ready = new Dependency(false);
|
|
81
|
+
let isDep2Ready = new Dependency(false);
|
|
82
|
+
let counter = new Dependency(0);
|
|
83
|
+
|
|
84
|
+
// function that implicityly uses dependency instances
|
|
85
|
+
let cachedFunction = () => {
|
|
86
|
+
if (isDep1Ready.value && isDep2Ready.value) {
|
|
87
|
+
return counter.value;
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function subscribe() {
|
|
93
|
+
for await (const value of reaction(cachedFunction)) {
|
|
94
|
+
reactionFn();
|
|
95
|
+
}
|
|
96
|
+
exitFn();
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
The reaction is declared deprecated because a return value caching does not
|
|
100
|
+
use optimal strategy. Moreover, ideally, reaction should give back a
|
|
101
|
+
dependency instance or something like it, that can provide an ability
|
|
102
|
+
to be an observable for another reaction.
|
|
103
|
+
|
|
75
104
|
#### Framework integrations
|
|
76
105
|
##### React
|
|
77
106
|
Of course, there is a React integration via stream hooks.
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { PromiseConfiguration } from "../promise-configuration.js";
|
|
2
|
-
import { DependencyStream } from "./dependency.stream.js";
|
|
3
2
|
import { baseComparer } from "./utils.js";
|
|
4
|
-
import {
|
|
3
|
+
import { collectDep } from "./global.js";
|
|
5
4
|
export class Dependency {
|
|
6
5
|
_value;
|
|
7
6
|
reactionPromise;
|
|
@@ -16,6 +15,8 @@ export class Dependency {
|
|
|
16
15
|
};
|
|
17
16
|
}
|
|
18
17
|
_set(v) {
|
|
18
|
+
if (this.done)
|
|
19
|
+
return;
|
|
19
20
|
if (this.config.withCustomEquality(this._value, v)) {
|
|
20
21
|
return;
|
|
21
22
|
}
|
|
@@ -29,44 +30,39 @@ export class Dependency {
|
|
|
29
30
|
this._set(v);
|
|
30
31
|
}
|
|
31
32
|
get value() {
|
|
32
|
-
|
|
33
|
+
collectDep(this);
|
|
33
34
|
return this._value;
|
|
34
35
|
}
|
|
35
|
-
|
|
36
|
-
return
|
|
36
|
+
get done() {
|
|
37
|
+
return this.abortPromise.isFulfilled;
|
|
37
38
|
}
|
|
38
39
|
[Symbol.asyncIterator](thisStreamConfig = {}) {
|
|
39
|
-
const
|
|
40
|
-
const externalPromises = [totalDispose.promise];
|
|
40
|
+
const externalPromises = [];
|
|
41
41
|
let firstPromise;
|
|
42
42
|
const withReactionOnSubscribe = this.config.withReactionOnSubscribe || thisStreamConfig.withReactionOnSubscribe;
|
|
43
43
|
if (withReactionOnSubscribe) {
|
|
44
44
|
firstPromise = new PromiseConfiguration();
|
|
45
|
-
firstPromise.resolve(
|
|
45
|
+
firstPromise.resolve();
|
|
46
46
|
externalPromises.push(firstPromise.promise);
|
|
47
47
|
}
|
|
48
48
|
if (thisStreamConfig.externalDispose) {
|
|
49
49
|
externalPromises.push(thisStreamConfig.externalDispose.promise);
|
|
50
50
|
}
|
|
51
|
-
let isDisposed = false;
|
|
52
51
|
const owner = this;
|
|
52
|
+
let done = false;
|
|
53
53
|
return {
|
|
54
54
|
owner,
|
|
55
55
|
dispose: owner.dispose.bind(owner),
|
|
56
56
|
get isDisposed() {
|
|
57
|
-
return
|
|
57
|
+
return done || owner.done;
|
|
58
58
|
},
|
|
59
59
|
next: async () => {
|
|
60
|
-
if (!this.reactionPromise) {
|
|
61
|
-
this.reactionPromise = new PromiseConfiguration();
|
|
62
|
-
this._set(this._value);
|
|
63
|
-
}
|
|
64
60
|
await Promise.race([
|
|
65
61
|
...externalPromises,
|
|
66
|
-
|
|
62
|
+
owner.next(),
|
|
67
63
|
]);
|
|
68
|
-
if (
|
|
69
|
-
|
|
64
|
+
if (this.done || thisStreamConfig.externalDispose?.isFulfilled) {
|
|
65
|
+
done = true;
|
|
70
66
|
return { done: true };
|
|
71
67
|
}
|
|
72
68
|
if (firstPromise) {
|
|
@@ -82,9 +78,40 @@ export class Dependency {
|
|
|
82
78
|
}
|
|
83
79
|
};
|
|
84
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* One race of value change and dependency dispose
|
|
83
|
+
* for all subscribers
|
|
84
|
+
*/
|
|
85
|
+
_race;
|
|
86
|
+
getOrCreateRace() {
|
|
87
|
+
if (!this._race) {
|
|
88
|
+
this.reactionPromise = this.reactionPromise ?? new PromiseConfiguration();
|
|
89
|
+
this._race = Promise.race([
|
|
90
|
+
this.abortPromise.promise,
|
|
91
|
+
this.reactionPromise.promise,
|
|
92
|
+
]);
|
|
93
|
+
}
|
|
94
|
+
return this._race;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Another subscribe for current race
|
|
98
|
+
*/
|
|
99
|
+
async next() {
|
|
100
|
+
let race = this.getOrCreateRace();
|
|
101
|
+
await race;
|
|
102
|
+
this._race = undefined;
|
|
103
|
+
if (this.done) {
|
|
104
|
+
return { done: true };
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
done: false,
|
|
108
|
+
get value() {
|
|
109
|
+
return this.value;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
85
113
|
dispose() {
|
|
86
114
|
this.abortPromise.resolve();
|
|
87
|
-
this.abortPromise = new PromiseConfiguration();
|
|
88
115
|
this.reactionPromise = undefined;
|
|
89
116
|
}
|
|
90
117
|
}
|
|
@@ -3,7 +3,7 @@ const global = {
|
|
|
3
3
|
dependencies: null
|
|
4
4
|
};
|
|
5
5
|
const notImplemented = new Error('Watching while watching is not implemented');
|
|
6
|
-
export function
|
|
6
|
+
export function runFnWithDepCollection(fn) {
|
|
7
7
|
if (global.watchFlag) {
|
|
8
8
|
throw notImplemented;
|
|
9
9
|
}
|
|
@@ -17,8 +17,10 @@ export function watchDeps(fn) {
|
|
|
17
17
|
result, deps
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
|
-
export function
|
|
20
|
+
export function collectDep(dep) {
|
|
21
21
|
if (!global.watchFlag)
|
|
22
22
|
return;
|
|
23
|
+
if (dep.done)
|
|
24
|
+
return;
|
|
23
25
|
global.dependencies.add(dep);
|
|
24
26
|
}
|
|
@@ -1,21 +1,35 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { symAI } from "../../constants.js";
|
|
1
|
+
import { runFnWithDepCollection } from "../global.js";
|
|
3
2
|
/**
|
|
4
3
|
* @deprecated
|
|
5
4
|
*/
|
|
6
5
|
export function reaction(fn) {
|
|
7
6
|
return {
|
|
8
7
|
[Symbol.asyncIterator]: () => {
|
|
9
|
-
let { result, deps } =
|
|
10
|
-
|
|
8
|
+
let { result, deps } = runFnWithDepCollection(fn);
|
|
9
|
+
let depsArray;
|
|
10
|
+
let beforeValues;
|
|
11
|
+
let obj = {
|
|
11
12
|
next: async () => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
await Promise.race(
|
|
15
|
-
(
|
|
16
|
-
|
|
13
|
+
depsArray = Array.from(deps);
|
|
14
|
+
beforeValues = depsArray.map(dep => dep.value);
|
|
15
|
+
await Promise.race(depsArray.map(dep => dep.next()));
|
|
16
|
+
let shouldRun = depsArray.some((dep, i) => dep.value !== beforeValues[i]);
|
|
17
|
+
if (shouldRun) {
|
|
18
|
+
({ result, deps } = runFnWithDepCollection(fn));
|
|
19
|
+
return { done: false, value: result };
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
for (let dep of deps) {
|
|
23
|
+
if (dep.done)
|
|
24
|
+
deps.delete(dep);
|
|
25
|
+
}
|
|
26
|
+
if (!deps.size)
|
|
27
|
+
return { done: true };
|
|
28
|
+
return obj.next();
|
|
29
|
+
}
|
|
17
30
|
}
|
|
18
31
|
};
|
|
32
|
+
return obj;
|
|
19
33
|
}
|
|
20
34
|
};
|
|
21
35
|
}
|
|
@@ -16,9 +16,22 @@ export interface IIsEquals<T> {
|
|
|
16
16
|
}
|
|
17
17
|
export type IAllStreamConfig<T> = {
|
|
18
18
|
withCustomEquality: IIsEquals<T>;
|
|
19
|
+
/**
|
|
20
|
+
* Reaction happens anyway right after current task
|
|
21
|
+
* wherever dependency was disposed
|
|
22
|
+
*/
|
|
19
23
|
withReactionOnSubscribe: boolean;
|
|
20
24
|
};
|
|
21
25
|
export type IThisStreamConfig = Partial<{
|
|
26
|
+
/**
|
|
27
|
+
* Reaction happens right after current task
|
|
28
|
+
* wherever dependency itself was disposed, but
|
|
29
|
+
* stream dispose has priority over first reaction
|
|
30
|
+
*/
|
|
22
31
|
withReactionOnSubscribe: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Dispose happens anyway right after current task,
|
|
34
|
+
* wherever value of dependency was changed
|
|
35
|
+
*/
|
|
23
36
|
externalDispose: PromiseConfiguration<any>;
|
|
24
37
|
}>;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { IAllStreamConfig, IStreamIterator, IThisStreamConfig } from "./contracts.ts";
|
|
2
|
-
import { DependencyStream } from "./dependency.stream.ts";
|
|
3
2
|
export declare class Dependency<T = any> {
|
|
4
3
|
private _value;
|
|
5
4
|
private reactionPromise;
|
|
@@ -9,7 +8,23 @@ export declare class Dependency<T = any> {
|
|
|
9
8
|
private _set;
|
|
10
9
|
set value(v: T);
|
|
11
10
|
get value(): T;
|
|
12
|
-
|
|
11
|
+
get done(): boolean;
|
|
13
12
|
[Symbol.asyncIterator](this: Dependency<T>, thisStreamConfig?: IThisStreamConfig): IStreamIterator<T>;
|
|
13
|
+
/**
|
|
14
|
+
* One race of value change and dependency dispose
|
|
15
|
+
* for all subscribers
|
|
16
|
+
*/
|
|
17
|
+
private _race;
|
|
18
|
+
private getOrCreateRace;
|
|
19
|
+
/**
|
|
20
|
+
* Another subscribe for current race
|
|
21
|
+
*/
|
|
22
|
+
next(): Promise<{
|
|
23
|
+
done: true;
|
|
24
|
+
value?: never;
|
|
25
|
+
} | {
|
|
26
|
+
done: boolean;
|
|
27
|
+
readonly value: T;
|
|
28
|
+
}>;
|
|
14
29
|
dispose(this: Dependency<T>): void;
|
|
15
30
|
}
|
|
@@ -3,9 +3,9 @@ declare const global: {
|
|
|
3
3
|
watchFlag: boolean;
|
|
4
4
|
dependencies: Set<Dependency>;
|
|
5
5
|
};
|
|
6
|
-
export declare function
|
|
6
|
+
export declare function runFnWithDepCollection<T>(fn: () => T): {
|
|
7
7
|
result: T;
|
|
8
8
|
deps: typeof global.dependencies;
|
|
9
9
|
};
|
|
10
|
-
export declare function
|
|
10
|
+
export declare function collectDep(dep: Dependency): void;
|
|
11
11
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fbltd/async",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.25",
|
|
4
4
|
"description": "Miscellaneous async utils",
|
|
5
5
|
"homepage": "https://github.com/GlennMiller1991/async",
|
|
6
6
|
"type": "module",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"tsconfig.json"
|
|
22
22
|
],
|
|
23
23
|
"scripts": {
|
|
24
|
-
"clearDist": "rm
|
|
25
|
-
"clearModules": "rm
|
|
24
|
+
"clearDist": "rm -r dist || true",
|
|
25
|
+
"clearModules": "rm -r node_modules || true",
|
|
26
26
|
"clearAll": "npm run clearDist && npm run clearModules",
|
|
27
27
|
"build": "npm run clearAll && npm i && mkdir dist && tsc",
|
|
28
28
|
"test": "node --expose-gc node_modules/.bin/jest --config=./__tests__/jest.config.cjs",
|