@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 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 { setDep } from "./global.js";
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
- setDep(this);
33
+ collectDep(this);
33
34
  return this._value;
34
35
  }
35
- getStream() {
36
- return new DependencyStream(this);
36
+ get done() {
37
+ return this.abortPromise.isFulfilled;
37
38
  }
38
39
  [Symbol.asyncIterator](thisStreamConfig = {}) {
39
- const totalDispose = this.abortPromise;
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(this.value);
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 isDisposed;
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
- this.reactionPromise.promise,
62
+ owner.next(),
67
63
  ]);
68
- if (totalDispose.isFulfilled || thisStreamConfig.externalDispose?.isFulfilled) {
69
- isDisposed = true;
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 watchDeps(fn) {
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 setDep(dep) {
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
  }
@@ -0,0 +1,4 @@
1
+ import { DependencyStream } from "../dependency.stream.js";
2
+ export function getStream(dep) {
3
+ return new DependencyStream(dep);
4
+ }
@@ -1,6 +1,7 @@
1
1
  import { symAI } from "../../constants.js";
2
+ import { getStream } from "./get.stream.js";
2
3
  export function onceStream(dep) {
3
- const stream = dep.getStream();
4
+ const stream = getStream(dep);
4
5
  const iterator = stream[symAI]();
5
6
  return {
6
7
  get isDisposed() {
@@ -1,21 +1,35 @@
1
- import { watchDeps } from "../global.js";
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 } = watchDeps(fn);
10
- return {
8
+ let { result, deps } = runFnWithDepCollection(fn);
9
+ let depsArray;
10
+ let beforeValues;
11
+ let obj = {
11
12
  next: async () => {
12
- const dependencies = Array.from(deps);
13
- const streams = dependencies.map(dep => dep[symAI]());
14
- await Promise.race(streams.map(s => s.next()));
15
- ({ result, deps } = watchDeps(fn));
16
- return { done: false, value: result };
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
- getStream(this: Dependency<T>): DependencyStream<T>;
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 watchDeps<T>(fn: () => T): {
6
+ export declare function runFnWithDepCollection<T>(fn: () => T): {
7
7
  result: T;
8
8
  deps: typeof global.dependencies;
9
9
  };
10
- export declare function setDep(dep: Dependency): void;
10
+ export declare function collectDep(dep: Dependency): void;
11
11
  export {};
@@ -0,0 +1,3 @@
1
+ import { DependencyStream } from "../dependency.stream.ts";
2
+ import { Dependency } from "../dependency.ts";
3
+ export declare function getStream<T>(dep: Dependency<T>): DependencyStream<T>;
@@ -4,7 +4,10 @@
4
4
  export declare function reaction<T>(fn: () => T): {
5
5
  [Symbol.asyncIterator]: () => {
6
6
  next: () => Promise<{
7
- done: boolean;
7
+ done: true;
8
+ value?: never;
9
+ } | {
10
+ done: false;
8
11
  value: T;
9
12
  }>;
10
13
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fbltd/async",
3
- "version": "1.0.23",
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 dist -r || true",
25
- "clearModules": "rm node_modules -r || true",
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",