@fbltd/async 1.0.24 → 1.0.26

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
@@ -36,7 +36,6 @@ There is no any management of passed data for resolving.
36
36
  Returned promise is usual ES promise so it is impossible to fulfill promise twice.
37
37
 
38
38
  ### DependencyStream
39
- #### core
40
39
  Implementation of reactive model leveraging native JavaScript async features like
41
40
  Promises, (async) iterators and generators.
42
41
  The version is 0.0.x so keep it in mind
@@ -71,7 +70,34 @@ setInterval(() => {
71
70
  ##### onceStream
72
71
  ##### raceStream
73
72
  ##### next
73
+
74
74
  ##### reaction
75
+ The most powerful util among all stream utils.
76
+ Function is watching only for actual dependencies and does not react
77
+ for dependencies that do not affect result value. Since the reaction
78
+ provides dependency instance, of course, there is a function result caching:
79
+ ```typescript
80
+ let isDep1Ready = new Dependency(false);
81
+ let isDep2Ready = new Dependency(false);
82
+ let counter = new Dependency(0);
83
+
84
+ // function that uses dependency instances
85
+ let watchFn = () => {
86
+ // Take a look at IF condition -
87
+ // value can be changed only if dep1 and dep2 is ready
88
+ if (isDep1Ready.value && isDep2Ready.value) {
89
+ return counter.value;
90
+ }
91
+ return undefined;
92
+ }
93
+
94
+ async function subscribe() {
95
+ for await (const value of reaction(watchFn)) {
96
+ reactionFn();
97
+ }
98
+ exitFn();
99
+ }
100
+ ```
75
101
 
76
102
  #### Framework integrations
77
103
  ##### React
@@ -0,0 +1,10 @@
1
+ import { Dependency } from "./dependency.js";
2
+ import { reaction } from "./stream-utils/index.js";
3
+ export class DepFactory {
4
+ static ofValue(value, config) {
5
+ return new Dependency(value, config);
6
+ }
7
+ static ofReaction(fn, config) {
8
+ return reaction(fn, config);
9
+ }
10
+ }
@@ -1,7 +1,7 @@
1
1
  import { PromiseConfiguration } from "../promise-configuration.js";
2
- import { DependencyStream } from "./dependency.stream.js";
3
2
  import { baseComparer } from "./utils.js";
4
3
  import { collectDep } from "./global.js";
4
+ import { symAI } from "../constants.js";
5
5
  export class Dependency {
6
6
  _value;
7
7
  reactionPromise;
@@ -16,6 +16,8 @@ export class Dependency {
16
16
  };
17
17
  }
18
18
  _set(v) {
19
+ if (this.done)
20
+ return;
19
21
  if (this.config.withCustomEquality(this._value, v)) {
20
22
  return;
21
23
  }
@@ -32,38 +34,36 @@ export class Dependency {
32
34
  collectDep(this);
33
35
  return this._value;
34
36
  }
35
- getStream() {
36
- return new DependencyStream(this);
37
+ get done() {
38
+ return this.abortPromise.isFulfilled;
37
39
  }
38
- [Symbol.asyncIterator](thisStreamConfig = {}) {
39
- const totalDispose = this.abortPromise;
40
- const externalPromises = [totalDispose.promise];
40
+ [symAI](thisStreamConfig = {}) {
41
+ const externalPromises = [];
41
42
  let firstPromise;
42
43
  const withReactionOnSubscribe = this.config.withReactionOnSubscribe || thisStreamConfig.withReactionOnSubscribe;
43
44
  if (withReactionOnSubscribe) {
44
45
  firstPromise = new PromiseConfiguration();
45
- firstPromise.resolve(this.value);
46
+ firstPromise.resolve();
46
47
  externalPromises.push(firstPromise.promise);
47
48
  }
48
49
  if (thisStreamConfig.externalDispose) {
49
50
  externalPromises.push(thisStreamConfig.externalDispose.promise);
50
51
  }
51
- let isDisposed = false;
52
52
  const owner = this;
53
+ let done = false;
53
54
  return {
54
55
  owner,
55
56
  dispose: owner.dispose.bind(owner),
56
57
  get isDisposed() {
57
- return isDisposed;
58
+ return done || owner.done;
58
59
  },
59
60
  next: async () => {
60
- this.reactionPromise = this.reactionPromise ?? new PromiseConfiguration();
61
61
  await Promise.race([
62
62
  ...externalPromises,
63
- this.reactionPromise.promise,
63
+ owner.next(),
64
64
  ]);
65
- if (totalDispose.isFulfilled || thisStreamConfig.externalDispose?.isFulfilled) {
66
- isDisposed = true;
65
+ if (this.done || thisStreamConfig.externalDispose?.isFulfilled) {
66
+ done = true;
67
67
  return { done: true };
68
68
  }
69
69
  if (firstPromise) {
@@ -79,21 +79,29 @@ export class Dependency {
79
79
  }
80
80
  };
81
81
  }
82
+ /**
83
+ * One race of value change and dependency dispose
84
+ * for all subscribers
85
+ */
82
86
  _race;
83
- async next(ref = {}) {
84
- const abortPromise = this.abortPromise;
87
+ getOrCreateRace() {
85
88
  if (!this._race) {
86
89
  this.reactionPromise = this.reactionPromise ?? new PromiseConfiguration();
87
90
  this._race = Promise.race([
88
- abortPromise.promise,
91
+ this.abortPromise.promise,
89
92
  this.reactionPromise.promise,
90
93
  ]);
91
94
  }
92
- let race = this._race;
95
+ return this._race;
96
+ }
97
+ /**
98
+ * Another subscribe for current race
99
+ */
100
+ async next() {
101
+ let race = this.getOrCreateRace();
93
102
  await race;
94
103
  this._race = undefined;
95
- if (abortPromise.isFulfilled) {
96
- ref.done = true;
104
+ if (this.done) {
97
105
  return { done: true };
98
106
  }
99
107
  return {
@@ -103,9 +111,11 @@ export class Dependency {
103
111
  }
104
112
  };
105
113
  }
114
+ get disposePromise() {
115
+ return this.abortPromise.promise;
116
+ }
106
117
  dispose() {
107
118
  this.abortPromise.resolve();
108
- this.abortPromise = new PromiseConfiguration();
109
119
  this.reactionPromise = undefined;
110
120
  }
111
121
  }
@@ -20,5 +20,7 @@ export function runFnWithDepCollection(fn) {
20
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,4 +1,5 @@
1
1
  export * from "./dependency.js";
2
2
  export * from "./dependency.stream.js";
3
+ export * from "./dep.factory.js";
3
4
  export * from "./stream-utils/index.js";
4
5
  export * from "./integrations/index.js";
@@ -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,24 +1,44 @@
1
1
  import { runFnWithDepCollection } from "../global.js";
2
- /**
3
- * @deprecated
4
- */
5
- export function reaction(fn) {
6
- const ref = { done: false };
7
- return {
8
- [Symbol.asyncIterator]: () => {
9
- let { result, deps } = runFnWithDepCollection(fn);
10
- return {
11
- next: async () => {
12
- await Promise
13
- .race(Array
14
- .from(deps)
15
- .map(dep => dep.next(ref)));
16
- if (ref.done)
17
- return ref;
18
- ({ result, deps } = runFnWithDepCollection(fn));
19
- return { done: false, value: result };
20
- }
21
- };
2
+ import { Dependency } from "../dependency.js";
3
+ import { symAI } from "../../constants.js";
4
+ export function reaction(fn, config) {
5
+ let { result, deps } = runFnWithDepCollection(fn);
6
+ const dep = new Dependency(result, config);
7
+ async function subscribe() {
8
+ const stream = { [symAI]: () => {
9
+ let depsArray;
10
+ let beforeValues;
11
+ let obj = {
12
+ next: async () => {
13
+ depsArray = Array.from(deps);
14
+ beforeValues = depsArray.map(dep => dep.value);
15
+ const promises = depsArray.map(dep => dep.next());
16
+ promises.push(dep.disposePromise);
17
+ await Promise.race(depsArray.map(dep => dep.next()));
18
+ if (dep.done)
19
+ return { done: true };
20
+ let shouldRun = depsArray.some((dep, i) => dep.value !== beforeValues[i]);
21
+ if (shouldRun) {
22
+ ({ result, deps } = runFnWithDepCollection(fn));
23
+ return { done: false, value: result };
24
+ }
25
+ else {
26
+ for (let dep of deps) {
27
+ if (dep.done)
28
+ deps.delete(dep);
29
+ }
30
+ if (!deps.size)
31
+ return { done: true };
32
+ return obj.next();
33
+ }
34
+ }
35
+ };
36
+ return obj;
37
+ } };
38
+ for await (let value of stream) {
39
+ dep.value = value;
22
40
  }
23
- };
41
+ }
42
+ subscribe().then(() => dep.dispose());
43
+ return dep;
24
44
  }
@@ -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
  }>;
@@ -0,0 +1,6 @@
1
+ import { Dependency } from "./dependency.ts";
2
+ import { IAllStreamConfig } from "./contracts.ts";
3
+ export declare abstract class DepFactory {
4
+ static ofValue<T>(value: T, config?: Partial<IAllStreamConfig<T>>): Dependency<T>;
5
+ static ofReaction<T>(fn: () => T, config?: Partial<IAllStreamConfig<T>>): Dependency<T>;
6
+ }
@@ -1,5 +1,5 @@
1
1
  import { IAllStreamConfig, IStreamIterator, IThisStreamConfig } from "./contracts.ts";
2
- import { DependencyStream } from "./dependency.stream.ts";
2
+ import { symAI } from "../constants.ts";
3
3
  export declare class Dependency<T = any> {
4
4
  private _value;
5
5
  private reactionPromise;
@@ -9,17 +9,24 @@ export declare class Dependency<T = any> {
9
9
  private _set;
10
10
  set value(v: T);
11
11
  get value(): T;
12
- getStream(this: Dependency<T>): DependencyStream<T>;
13
- [Symbol.asyncIterator](this: Dependency<T>, thisStreamConfig?: IThisStreamConfig): IStreamIterator<T>;
12
+ get done(): boolean;
13
+ [symAI](this: Dependency<T>, thisStreamConfig?: IThisStreamConfig): IStreamIterator<T>;
14
+ /**
15
+ * One race of value change and dependency dispose
16
+ * for all subscribers
17
+ */
14
18
  private _race;
15
- next(ref?: {
16
- done?: boolean;
17
- }): Promise<{
19
+ private getOrCreateRace;
20
+ /**
21
+ * Another subscribe for current race
22
+ */
23
+ next(): Promise<{
18
24
  done: true;
19
25
  value?: never;
20
26
  } | {
21
27
  done: boolean;
22
28
  readonly value: T;
23
29
  }>;
30
+ get disposePromise(): Promise<void>;
24
31
  dispose(this: Dependency<T>): void;
25
32
  }
@@ -1,4 +1,5 @@
1
1
  export * from './dependency.ts';
2
2
  export * from './dependency.stream.ts';
3
+ export * from './dep.factory.ts';
3
4
  export * from './stream-utils/index.ts';
4
5
  export * from './integrations/index.ts';
@@ -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>;
@@ -1,14 +1,3 @@
1
- /**
2
- * @deprecated
3
- */
4
- export declare function reaction<T>(fn: () => T): {
5
- [Symbol.asyncIterator]: () => {
6
- next: () => Promise<{
7
- done: true;
8
- value?: never;
9
- } | {
10
- done: boolean;
11
- value: T;
12
- }>;
13
- };
14
- };
1
+ import { Dependency } from "../dependency.ts";
2
+ import { IAllStreamConfig } from "../contracts.js";
3
+ export declare function reaction<T>(fn: () => T, config?: Partial<IAllStreamConfig<T>>): Dependency<T>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fbltd/async",
3
- "version": "1.0.24",
3
+ "version": "1.0.26",
4
4
  "description": "Miscellaneous async utils",
5
5
  "homepage": "https://github.com/GlennMiller1991/async",
6
6
  "type": "module",