@thi.ng/fibers 0.1.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/CHANGELOG.md +22 -0
- package/LICENSE +201 -0
- package/README.md +246 -0
- package/api.d.ts +62 -0
- package/api.js +19 -0
- package/fiber.d.ts +204 -0
- package/fiber.js +347 -0
- package/index.d.ts +4 -0
- package/index.js +3 -0
- package/ops.d.ts +130 -0
- package/ops.js +198 -0
- package/package.json +86 -0
package/fiber.d.ts
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import type { Event as $Event, Fn, Fn0, IDeref, IID, IIDGen, INotify, Listener, Nullable } from "@thi.ng/api";
|
|
2
|
+
import type { ILogger } from "@thi.ng/logger";
|
|
3
|
+
import { type FiberEventType, type FiberFactory, type FiberOpts, type MaybeFiber, type State } from "./api.js";
|
|
4
|
+
export declare const setDefaultIDGen: (gen: IIDGen<string>) => IIDGen<string>;
|
|
5
|
+
export declare class Fiber<T = any> implements IDeref<T | undefined>, IID<string>, INotify<FiberEventType> {
|
|
6
|
+
/**
|
|
7
|
+
* This fiber's user provided or generated ID.
|
|
8
|
+
*/
|
|
9
|
+
readonly id: string;
|
|
10
|
+
/**
|
|
11
|
+
* This fiber's parent.
|
|
12
|
+
*/
|
|
13
|
+
readonly parent?: Fiber;
|
|
14
|
+
gen: Nullable<Generator<unknown, T>>;
|
|
15
|
+
idgen?: IIDGen<string>;
|
|
16
|
+
state: State;
|
|
17
|
+
children?: Fiber[];
|
|
18
|
+
value?: T;
|
|
19
|
+
error?: Error;
|
|
20
|
+
logger?: ILogger;
|
|
21
|
+
user?: Partial<Pick<FiberOpts, "init" | "deinit" | "catch">>;
|
|
22
|
+
constructor(gen?: Nullable<FiberFactory<T> | Generator<unknown, T>>, opts?: Partial<FiberOpts>);
|
|
23
|
+
/**
|
|
24
|
+
* Co-routine which blocks whilst this fiber (incl. its children) is active.
|
|
25
|
+
* Then return this fiber's value.
|
|
26
|
+
*/
|
|
27
|
+
[Symbol.iterator](): Generator<undefined, T | undefined, unknown>;
|
|
28
|
+
/**
|
|
29
|
+
* Returns this fiber's result value (if any). Only available if the fiber
|
|
30
|
+
* completed successfully and produced a value (either by returning a value
|
|
31
|
+
* from the fiber's generator or externally via {@link Fiber.done}).
|
|
32
|
+
*/
|
|
33
|
+
deref(): T | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* Returns true if this fiber is still in new or active state (i.e. still
|
|
36
|
+
* can be processed).
|
|
37
|
+
*/
|
|
38
|
+
isActive(): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Returns child fiber for given `id`.
|
|
41
|
+
*
|
|
42
|
+
* @param id
|
|
43
|
+
*/
|
|
44
|
+
childForID(id: string): Fiber<any> | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* Adds given `body` as child process to this fiber. If not already a
|
|
47
|
+
* {@link Fiber} instance, it will be wrapped as such incl. with given
|
|
48
|
+
* options. `opts` are only used for this latter case and will inherit this
|
|
49
|
+
* (parent) fiber's {@link FiberOpts.logger} and {@link FiberOpts.idgen} as
|
|
50
|
+
* defaults. Returns child fiber.
|
|
51
|
+
*
|
|
52
|
+
* @remarks
|
|
53
|
+
* Child fibers are only processed when the parent is processed (e.g. via
|
|
54
|
+
* {@link Fiber.run} or via `yield* fiber`). Also see {@link Fiber.join} to
|
|
55
|
+
* wait for all child processes to finish.
|
|
56
|
+
*
|
|
57
|
+
* Non-active child process (i.e. finished, cancelled or errored) are
|
|
58
|
+
* automatically removed from the parent. If the child fiber is needed for
|
|
59
|
+
* future inspection, the return value of `fork()` should be stored by the
|
|
60
|
+
* user. Whilst still active, child fibers can also be looked up via
|
|
61
|
+
* {@link Fiber.childForID}.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* fiber(function* (ctx) {
|
|
66
|
+
* console.log("main start")
|
|
67
|
+
* // create 2 child processes
|
|
68
|
+
* ctx.fork(function* () {
|
|
69
|
+
* console.log("child1 start");
|
|
70
|
+
* yield* wait(500);
|
|
71
|
+
* console.log("child1 end");
|
|
72
|
+
* });
|
|
73
|
+
* // second process will take longer than first
|
|
74
|
+
* ctx.fork(function* () {
|
|
75
|
+
* console.log("child2 start");
|
|
76
|
+
* yield* wait(1000);
|
|
77
|
+
* console.log("child2 end");
|
|
78
|
+
* });
|
|
79
|
+
* // wait for children to complete
|
|
80
|
+
* yield* ctx.join();
|
|
81
|
+
* console.log("main end")
|
|
82
|
+
* }).run();
|
|
83
|
+
*
|
|
84
|
+
* // main start
|
|
85
|
+
* // child1 start
|
|
86
|
+
* // child2 start
|
|
87
|
+
* // child1 end
|
|
88
|
+
* // child2 end
|
|
89
|
+
* // main end
|
|
90
|
+
* ```
|
|
91
|
+
*
|
|
92
|
+
* @param f
|
|
93
|
+
* @param opts
|
|
94
|
+
*/
|
|
95
|
+
fork<F>(body?: Nullable<MaybeFiber<F>>, opts?: Partial<FiberOpts>): Fiber<F>;
|
|
96
|
+
/**
|
|
97
|
+
* Calls {@link Fiber.fork} for all given fibers and returns them as array.
|
|
98
|
+
*
|
|
99
|
+
* @remarks
|
|
100
|
+
* Also see {@link Fiber.join} to wait for all child processes to complete.
|
|
101
|
+
*
|
|
102
|
+
* @param fibers
|
|
103
|
+
*/
|
|
104
|
+
forkAll(...fibers: MaybeFiber[]): Fiber[];
|
|
105
|
+
/**
|
|
106
|
+
* Waits for all child processes to complete/terminate. Use as `yield*
|
|
107
|
+
* fiber.join()`.
|
|
108
|
+
*
|
|
109
|
+
* @remarks
|
|
110
|
+
* See {@link Fiber.fork}, {@link Fiber.forkAll}.
|
|
111
|
+
*
|
|
112
|
+
*/
|
|
113
|
+
join(): Generator<undefined, void, unknown>;
|
|
114
|
+
/**
|
|
115
|
+
* Processes a single iteration of this fiber and any of its children. Does
|
|
116
|
+
* nothing if the fiber is not active anymore. Returns fiber's state.
|
|
117
|
+
*
|
|
118
|
+
* @remarks
|
|
119
|
+
* New, ininitialized fibers are first initialized via {@link Fiber.init}.
|
|
120
|
+
* Likewise, when fibers are terminated (for whatever reason), they will be
|
|
121
|
+
* de-initialized via {@link Fiber.deinit}. For all of these cases
|
|
122
|
+
* (init/deinit), hooks for user customization are provided via
|
|
123
|
+
* {@link FiberOpts.init}, {@link FiberOpts.deinit} and
|
|
124
|
+
* {@link FiberOpts.catch}.
|
|
125
|
+
*/
|
|
126
|
+
next(): State;
|
|
127
|
+
protected init(): void;
|
|
128
|
+
protected deinit(): void;
|
|
129
|
+
/**
|
|
130
|
+
* Cancels further processing of this fiber and its children (if any). Calls
|
|
131
|
+
* {@link Fiber.deinit} and emits {@link EVENT_FIBER_CANCELED} event.
|
|
132
|
+
*
|
|
133
|
+
* @remarks
|
|
134
|
+
* Function is a no-op if the fiber is not active anymore.
|
|
135
|
+
*/
|
|
136
|
+
cancel(): void;
|
|
137
|
+
/**
|
|
138
|
+
* Stops further processing of this fiber and its children (if any) and sets
|
|
139
|
+
* this fiber's value to given `value`. Calls {@link Fiber.deinit} and emits
|
|
140
|
+
* {@link EVENT_FIBER_DONE} event.
|
|
141
|
+
*
|
|
142
|
+
* @remarks
|
|
143
|
+
* Function is a no-op if the fiber is not active anymore.
|
|
144
|
+
*
|
|
145
|
+
* @param value
|
|
146
|
+
*/
|
|
147
|
+
done(value?: T): void;
|
|
148
|
+
/**
|
|
149
|
+
* Stops further processing of this fiber, cancels all child processes (if
|
|
150
|
+
* any) and sets this fiber's {@link Fiber.error} value to given `error`.
|
|
151
|
+
* Calls {@link Fiber.deinit} and emits {@link EVENT_FIBER_ERROR} event.
|
|
152
|
+
*
|
|
153
|
+
* @remarks
|
|
154
|
+
* Function is a no-op if the fiber already is in an error state. See
|
|
155
|
+
* {@link FiberOpts.catch} for details about user provided error handling
|
|
156
|
+
* and interception logic.
|
|
157
|
+
*
|
|
158
|
+
* @param err
|
|
159
|
+
*/
|
|
160
|
+
catch(err: Error): void;
|
|
161
|
+
addListener(id: FiberEventType, fn: Listener<FiberEventType>, scope?: any): boolean;
|
|
162
|
+
removeListener(id: FiberEventType, fn: Listener<FiberEventType>, scope?: any): boolean;
|
|
163
|
+
notify(event: $Event<FiberEventType>): boolean;
|
|
164
|
+
/**
|
|
165
|
+
* Calls {@link Fiber.runWith} using default loop handlers
|
|
166
|
+
* (`requestAnimationFrame()` in browsers, `setTimeout(fn, 16)` otherwise).
|
|
167
|
+
*/
|
|
168
|
+
run(): this;
|
|
169
|
+
/**
|
|
170
|
+
* Starts fiber execution using the provided higher-order loop/interval
|
|
171
|
+
* `handler` (e.g. see {@link Fiber.start}).
|
|
172
|
+
*
|
|
173
|
+
* @remarks
|
|
174
|
+
* That given `handler` is used to repeatedly schedule the next execution of
|
|
175
|
+
* {@link Fiber.next} (indirectly, via a zero-arg helper function passed to
|
|
176
|
+
* the `handler`).
|
|
177
|
+
*
|
|
178
|
+
* Note: **Do not use `setInterval` instead of `setTimeout`**. The given
|
|
179
|
+
* `handler` must only manage a single execution step, not multiple.
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```ts
|
|
183
|
+
* // start with custom higher frequency handler
|
|
184
|
+
* fiber(function*() {
|
|
185
|
+
* while(true) {
|
|
186
|
+
* console.log("hello");
|
|
187
|
+
* yield;
|
|
188
|
+
* }
|
|
189
|
+
* }).runWith(setImmediate);
|
|
190
|
+
* ```
|
|
191
|
+
*
|
|
192
|
+
* @param handler
|
|
193
|
+
*/
|
|
194
|
+
runWith(handler: Fn<Fn0<void>, void>): this;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Functional syntax sugar for {@link Fiber} constructor. The `opts` are only
|
|
198
|
+
* used if given a generator or {@link FiberFactory}.
|
|
199
|
+
*
|
|
200
|
+
* @param fiber
|
|
201
|
+
* @param opts
|
|
202
|
+
*/
|
|
203
|
+
export declare const fiber: <T>(fiber?: Nullable<MaybeFiber<T>>, opts?: Partial<FiberOpts>) => Fiber<T>;
|
|
204
|
+
//# sourceMappingURL=fiber.d.ts.map
|
package/fiber.js
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import { INotifyMixin } from "@thi.ng/api/mixins/inotify";
|
|
3
|
+
import { isFunction } from "@thi.ng/checks/is-function";
|
|
4
|
+
import { illegalState } from "@thi.ng/errors/illegal-state";
|
|
5
|
+
import { monotonic, prefixed } from "@thi.ng/idgen";
|
|
6
|
+
import { EVENT_FIBER_CANCELED, EVENT_FIBER_DONE, EVENT_FIBER_ERROR, STATE_ACTIVE, STATE_CANCELED, STATE_DONE, STATE_ERROR, STATE_NEW, } from "./api.js";
|
|
7
|
+
let DEFAULT_ID_GEN = prefixed("fib-", monotonic());
|
|
8
|
+
export const setDefaultIDGen = (gen) => (DEFAULT_ID_GEN = gen);
|
|
9
|
+
const NO_RESULT = { done: false, value: undefined };
|
|
10
|
+
export let Fiber = class Fiber {
|
|
11
|
+
constructor(gen, opts) {
|
|
12
|
+
this.state = STATE_NEW;
|
|
13
|
+
this.value = undefined;
|
|
14
|
+
if (opts) {
|
|
15
|
+
this.logger = opts.logger;
|
|
16
|
+
this.parent = opts.parent;
|
|
17
|
+
this.user = {
|
|
18
|
+
init: opts.init,
|
|
19
|
+
deinit: opts.deinit,
|
|
20
|
+
catch: opts.catch,
|
|
21
|
+
};
|
|
22
|
+
if (opts.id) {
|
|
23
|
+
this.id = opts.id;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
this.idgen = opts.idgen || DEFAULT_ID_GEN;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
this.idgen = DEFAULT_ID_GEN;
|
|
31
|
+
}
|
|
32
|
+
if (!this.id && this.idgen)
|
|
33
|
+
this.id = this.idgen.next();
|
|
34
|
+
this.gen = isFunction(gen) ? gen(this) : gen;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Co-routine which blocks whilst this fiber (incl. its children) is active.
|
|
38
|
+
* Then return this fiber's value.
|
|
39
|
+
*/
|
|
40
|
+
*[Symbol.iterator]() {
|
|
41
|
+
while (this.state <= STATE_ACTIVE && this.next() <= STATE_ACTIVE)
|
|
42
|
+
yield;
|
|
43
|
+
return this.value;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Returns this fiber's result value (if any). Only available if the fiber
|
|
47
|
+
* completed successfully and produced a value (either by returning a value
|
|
48
|
+
* from the fiber's generator or externally via {@link Fiber.done}).
|
|
49
|
+
*/
|
|
50
|
+
deref() {
|
|
51
|
+
return this.value;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Returns true if this fiber is still in new or active state (i.e. still
|
|
55
|
+
* can be processed).
|
|
56
|
+
*/
|
|
57
|
+
isActive() {
|
|
58
|
+
return this.state <= STATE_ACTIVE;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Returns child fiber for given `id`.
|
|
62
|
+
*
|
|
63
|
+
* @param id
|
|
64
|
+
*/
|
|
65
|
+
childForID(id) {
|
|
66
|
+
return this.children?.find((f) => f.id === id);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Adds given `body` as child process to this fiber. If not already a
|
|
70
|
+
* {@link Fiber} instance, it will be wrapped as such incl. with given
|
|
71
|
+
* options. `opts` are only used for this latter case and will inherit this
|
|
72
|
+
* (parent) fiber's {@link FiberOpts.logger} and {@link FiberOpts.idgen} as
|
|
73
|
+
* defaults. Returns child fiber.
|
|
74
|
+
*
|
|
75
|
+
* @remarks
|
|
76
|
+
* Child fibers are only processed when the parent is processed (e.g. via
|
|
77
|
+
* {@link Fiber.run} or via `yield* fiber`). Also see {@link Fiber.join} to
|
|
78
|
+
* wait for all child processes to finish.
|
|
79
|
+
*
|
|
80
|
+
* Non-active child process (i.e. finished, cancelled or errored) are
|
|
81
|
+
* automatically removed from the parent. If the child fiber is needed for
|
|
82
|
+
* future inspection, the return value of `fork()` should be stored by the
|
|
83
|
+
* user. Whilst still active, child fibers can also be looked up via
|
|
84
|
+
* {@link Fiber.childForID}.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* fiber(function* (ctx) {
|
|
89
|
+
* console.log("main start")
|
|
90
|
+
* // create 2 child processes
|
|
91
|
+
* ctx.fork(function* () {
|
|
92
|
+
* console.log("child1 start");
|
|
93
|
+
* yield* wait(500);
|
|
94
|
+
* console.log("child1 end");
|
|
95
|
+
* });
|
|
96
|
+
* // second process will take longer than first
|
|
97
|
+
* ctx.fork(function* () {
|
|
98
|
+
* console.log("child2 start");
|
|
99
|
+
* yield* wait(1000);
|
|
100
|
+
* console.log("child2 end");
|
|
101
|
+
* });
|
|
102
|
+
* // wait for children to complete
|
|
103
|
+
* yield* ctx.join();
|
|
104
|
+
* console.log("main end")
|
|
105
|
+
* }).run();
|
|
106
|
+
*
|
|
107
|
+
* // main start
|
|
108
|
+
* // child1 start
|
|
109
|
+
* // child2 start
|
|
110
|
+
* // child1 end
|
|
111
|
+
* // child2 end
|
|
112
|
+
* // main end
|
|
113
|
+
* ```
|
|
114
|
+
*
|
|
115
|
+
* @param f
|
|
116
|
+
* @param opts
|
|
117
|
+
*/
|
|
118
|
+
fork(body, opts) {
|
|
119
|
+
if (!this.isActive())
|
|
120
|
+
illegalState(`fiber (id: ${this.id}) not active`);
|
|
121
|
+
const $fiber = fiber(body, {
|
|
122
|
+
parent: this,
|
|
123
|
+
logger: this.logger,
|
|
124
|
+
idgen: this.idgen,
|
|
125
|
+
...opts,
|
|
126
|
+
});
|
|
127
|
+
if (!this.children)
|
|
128
|
+
this.children = [];
|
|
129
|
+
this.children.push($fiber);
|
|
130
|
+
this.logger?.debug("forking", $fiber.id);
|
|
131
|
+
return $fiber;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Calls {@link Fiber.fork} for all given fibers and returns them as array.
|
|
135
|
+
*
|
|
136
|
+
* @remarks
|
|
137
|
+
* Also see {@link Fiber.join} to wait for all child processes to complete.
|
|
138
|
+
*
|
|
139
|
+
* @param fibers
|
|
140
|
+
*/
|
|
141
|
+
forkAll(...fibers) {
|
|
142
|
+
return fibers.map((f) => this.fork(f));
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Waits for all child processes to complete/terminate. Use as `yield*
|
|
146
|
+
* fiber.join()`.
|
|
147
|
+
*
|
|
148
|
+
* @remarks
|
|
149
|
+
* See {@link Fiber.fork}, {@link Fiber.forkAll}.
|
|
150
|
+
*
|
|
151
|
+
*/
|
|
152
|
+
*join() {
|
|
153
|
+
this.logger?.debug("waiting for children...");
|
|
154
|
+
while (this.children?.length)
|
|
155
|
+
yield;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Processes a single iteration of this fiber and any of its children. Does
|
|
159
|
+
* nothing if the fiber is not active anymore. Returns fiber's state.
|
|
160
|
+
*
|
|
161
|
+
* @remarks
|
|
162
|
+
* New, ininitialized fibers are first initialized via {@link Fiber.init}.
|
|
163
|
+
* Likewise, when fibers are terminated (for whatever reason), they will be
|
|
164
|
+
* de-initialized via {@link Fiber.deinit}. For all of these cases
|
|
165
|
+
* (init/deinit), hooks for user customization are provided via
|
|
166
|
+
* {@link FiberOpts.init}, {@link FiberOpts.deinit} and
|
|
167
|
+
* {@link FiberOpts.catch}.
|
|
168
|
+
*/
|
|
169
|
+
next() {
|
|
170
|
+
switch (this.state) {
|
|
171
|
+
case STATE_NEW:
|
|
172
|
+
this.init();
|
|
173
|
+
// explicit fallthrough
|
|
174
|
+
case STATE_ACTIVE:
|
|
175
|
+
try {
|
|
176
|
+
const { children } = this;
|
|
177
|
+
if (children) {
|
|
178
|
+
for (let i = 0, n = children.length; i < n;) {
|
|
179
|
+
const child = children[i];
|
|
180
|
+
if (child.state > STATE_ACTIVE ||
|
|
181
|
+
child.next() > STATE_ACTIVE) {
|
|
182
|
+
children.splice(i, 1);
|
|
183
|
+
n--;
|
|
184
|
+
}
|
|
185
|
+
else
|
|
186
|
+
i++;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const res = this.gen ? this.gen.next(this) : NO_RESULT;
|
|
190
|
+
if (res.done)
|
|
191
|
+
this.done(res.value);
|
|
192
|
+
}
|
|
193
|
+
catch (e) {
|
|
194
|
+
this.catch(e);
|
|
195
|
+
}
|
|
196
|
+
break;
|
|
197
|
+
default:
|
|
198
|
+
}
|
|
199
|
+
return this.state;
|
|
200
|
+
}
|
|
201
|
+
init() {
|
|
202
|
+
this.logger?.debug("init", this.id);
|
|
203
|
+
this.user?.init?.(this);
|
|
204
|
+
this.state = STATE_ACTIVE;
|
|
205
|
+
}
|
|
206
|
+
deinit() {
|
|
207
|
+
this.logger?.debug("deinit", this.id);
|
|
208
|
+
this.user?.deinit?.(this);
|
|
209
|
+
if (this.children)
|
|
210
|
+
this.children = undefined;
|
|
211
|
+
this.gen = null;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Cancels further processing of this fiber and its children (if any). Calls
|
|
215
|
+
* {@link Fiber.deinit} and emits {@link EVENT_FIBER_CANCELED} event.
|
|
216
|
+
*
|
|
217
|
+
* @remarks
|
|
218
|
+
* Function is a no-op if the fiber is not active anymore.
|
|
219
|
+
*/
|
|
220
|
+
cancel() {
|
|
221
|
+
if (!this.isActive())
|
|
222
|
+
return;
|
|
223
|
+
this.logger?.debug("cancel", this.id);
|
|
224
|
+
if (this.children) {
|
|
225
|
+
for (let child of this.children)
|
|
226
|
+
child.cancel();
|
|
227
|
+
}
|
|
228
|
+
this.deinit();
|
|
229
|
+
this.state = STATE_CANCELED;
|
|
230
|
+
this.idgen?.free(this.id);
|
|
231
|
+
this.notify({ id: EVENT_FIBER_CANCELED, target: this });
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Stops further processing of this fiber and its children (if any) and sets
|
|
235
|
+
* this fiber's value to given `value`. Calls {@link Fiber.deinit} and emits
|
|
236
|
+
* {@link EVENT_FIBER_DONE} event.
|
|
237
|
+
*
|
|
238
|
+
* @remarks
|
|
239
|
+
* Function is a no-op if the fiber is not active anymore.
|
|
240
|
+
*
|
|
241
|
+
* @param value
|
|
242
|
+
*/
|
|
243
|
+
done(value) {
|
|
244
|
+
if (!this.isActive())
|
|
245
|
+
return;
|
|
246
|
+
this.logger?.debug("done", this.id, value);
|
|
247
|
+
this.value = value;
|
|
248
|
+
if (this.children) {
|
|
249
|
+
for (let child of this.children)
|
|
250
|
+
child.done();
|
|
251
|
+
}
|
|
252
|
+
this.deinit();
|
|
253
|
+
this.state = STATE_DONE;
|
|
254
|
+
this.idgen?.free(this.id);
|
|
255
|
+
this.notify({ id: EVENT_FIBER_DONE, target: this, value });
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Stops further processing of this fiber, cancels all child processes (if
|
|
259
|
+
* any) and sets this fiber's {@link Fiber.error} value to given `error`.
|
|
260
|
+
* Calls {@link Fiber.deinit} and emits {@link EVENT_FIBER_ERROR} event.
|
|
261
|
+
*
|
|
262
|
+
* @remarks
|
|
263
|
+
* Function is a no-op if the fiber already is in an error state. See
|
|
264
|
+
* {@link FiberOpts.catch} for details about user provided error handling
|
|
265
|
+
* and interception logic.
|
|
266
|
+
*
|
|
267
|
+
* @param err
|
|
268
|
+
*/
|
|
269
|
+
catch(err) {
|
|
270
|
+
if (this.state >= STATE_ERROR || this.user?.catch?.(this, err))
|
|
271
|
+
return;
|
|
272
|
+
this.logger
|
|
273
|
+
? this.logger.severe(`error ${this.id}:`, err)
|
|
274
|
+
: console.warn(`error ${this.id}:`, err);
|
|
275
|
+
if (this.children) {
|
|
276
|
+
for (let child of this.children)
|
|
277
|
+
child.cancel();
|
|
278
|
+
}
|
|
279
|
+
this.state = STATE_ERROR;
|
|
280
|
+
this.error = err;
|
|
281
|
+
this.deinit();
|
|
282
|
+
this.idgen?.free(this.id);
|
|
283
|
+
this.notify({ id: EVENT_FIBER_ERROR, target: this, value: err });
|
|
284
|
+
}
|
|
285
|
+
// @ts-ignore mixin
|
|
286
|
+
// prettier-ignore
|
|
287
|
+
addListener(id, fn, scope) { }
|
|
288
|
+
// @ts-ignore mixin
|
|
289
|
+
// prettier-ignore
|
|
290
|
+
removeListener(id, fn, scope) { }
|
|
291
|
+
// @ts-ignore mixin
|
|
292
|
+
notify(event) { }
|
|
293
|
+
/**
|
|
294
|
+
* Calls {@link Fiber.runWith} using default loop handlers
|
|
295
|
+
* (`requestAnimationFrame()` in browsers, `setTimeout(fn, 16)` otherwise).
|
|
296
|
+
*/
|
|
297
|
+
run() {
|
|
298
|
+
return this.runWith(typeof requestAnimationFrame === "function"
|
|
299
|
+
? requestAnimationFrame
|
|
300
|
+
: (fn) => setTimeout(fn, 16));
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Starts fiber execution using the provided higher-order loop/interval
|
|
304
|
+
* `handler` (e.g. see {@link Fiber.start}).
|
|
305
|
+
*
|
|
306
|
+
* @remarks
|
|
307
|
+
* That given `handler` is used to repeatedly schedule the next execution of
|
|
308
|
+
* {@link Fiber.next} (indirectly, via a zero-arg helper function passed to
|
|
309
|
+
* the `handler`).
|
|
310
|
+
*
|
|
311
|
+
* Note: **Do not use `setInterval` instead of `setTimeout`**. The given
|
|
312
|
+
* `handler` must only manage a single execution step, not multiple.
|
|
313
|
+
*
|
|
314
|
+
* @example
|
|
315
|
+
* ```ts
|
|
316
|
+
* // start with custom higher frequency handler
|
|
317
|
+
* fiber(function*() {
|
|
318
|
+
* while(true) {
|
|
319
|
+
* console.log("hello");
|
|
320
|
+
* yield;
|
|
321
|
+
* }
|
|
322
|
+
* }).runWith(setImmediate);
|
|
323
|
+
* ```
|
|
324
|
+
*
|
|
325
|
+
* @param handler
|
|
326
|
+
*/
|
|
327
|
+
runWith(handler) {
|
|
328
|
+
this.logger?.debug(`running ${this.id}...`);
|
|
329
|
+
const loop = () => {
|
|
330
|
+
if (this.state <= STATE_ACTIVE && this.next() <= STATE_ACTIVE)
|
|
331
|
+
handler(loop);
|
|
332
|
+
};
|
|
333
|
+
loop();
|
|
334
|
+
return this;
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
Fiber = __decorate([
|
|
338
|
+
INotifyMixin
|
|
339
|
+
], Fiber);
|
|
340
|
+
/**
|
|
341
|
+
* Functional syntax sugar for {@link Fiber} constructor. The `opts` are only
|
|
342
|
+
* used if given a generator or {@link FiberFactory}.
|
|
343
|
+
*
|
|
344
|
+
* @param fiber
|
|
345
|
+
* @param opts
|
|
346
|
+
*/
|
|
347
|
+
export const fiber = (fiber, opts) => (fiber != null && fiber instanceof Fiber ? fiber : new Fiber(fiber, opts));
|
package/index.d.ts
ADDED
package/index.js
ADDED
package/ops.d.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { Fn0, Predicate } from "@thi.ng/api";
|
|
2
|
+
import { type FiberFactory, type FiberOpts, type MaybeFiber } from "./api.js";
|
|
3
|
+
import { Fiber } from "./fiber.js";
|
|
4
|
+
/**
|
|
5
|
+
* Returns co-routine which "blocks" for given number of milliseconds or
|
|
6
|
+
* indefinitely.
|
|
7
|
+
*
|
|
8
|
+
* @param delay
|
|
9
|
+
*/
|
|
10
|
+
export declare const wait: (delay?: number) => Fiber<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Returns ES6 generator which "blocks" for given number of frames.
|
|
13
|
+
*
|
|
14
|
+
* @param delay
|
|
15
|
+
*/
|
|
16
|
+
export declare function waitFrames(delay: number): Generator<undefined, void, unknown>;
|
|
17
|
+
/**
|
|
18
|
+
* Returns a fiber which executes given fibers in sequence until all are
|
|
19
|
+
* complete or one of them errored or got canceled.
|
|
20
|
+
*
|
|
21
|
+
* @param fibers
|
|
22
|
+
* @param opts
|
|
23
|
+
*/
|
|
24
|
+
export declare const sequence: (fibers: Iterable<Fiber | FiberFactory | Generator>, opts?: Partial<FiberOpts>) => Fiber<unknown>;
|
|
25
|
+
/**
|
|
26
|
+
* Returns a fiber which executes given fibers as child processes until **one**
|
|
27
|
+
* of them is finished/terminated. That child fiber itself will be the result.
|
|
28
|
+
*
|
|
29
|
+
* @remarks
|
|
30
|
+
* Also see {@link withTimeout}, {@link all}.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ta
|
|
34
|
+
* // wait until mouse click for max 5 seconds
|
|
35
|
+
* const res = yield* first([
|
|
36
|
+
* untilEvent(window, "click", { id: "click" }),
|
|
37
|
+
* wait(5000)
|
|
38
|
+
* ]);
|
|
39
|
+
*
|
|
40
|
+
* // one way to check result
|
|
41
|
+
* if (res.id === "click") { ... }
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @param fibers
|
|
45
|
+
* @param opts
|
|
46
|
+
*/
|
|
47
|
+
export declare const first: (fibers: (Fiber | FiberFactory | Generator)[], opts?: Partial<FiberOpts>) => Fiber<Fiber<any>>;
|
|
48
|
+
/**
|
|
49
|
+
* Returns a fiber which executes given fibers as child processes until **all**
|
|
50
|
+
* of them are finished/terminated.
|
|
51
|
+
*
|
|
52
|
+
* @remarks
|
|
53
|
+
* Also see {@link first}.
|
|
54
|
+
*
|
|
55
|
+
* @param fibers
|
|
56
|
+
* @param opts
|
|
57
|
+
*/
|
|
58
|
+
export declare const all: (fibers: MaybeFiber[], opts?: Partial<FiberOpts>) => Fiber<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Syntax sugar common use cases of {@link first} where a child fiber should be
|
|
61
|
+
* limited to a max. time period before giving up.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* // wait for fetch response max. 5 seconds
|
|
66
|
+
* const res = yield* withTimeout(untilPromise(fetch("example.json")), 5000);
|
|
67
|
+
*
|
|
68
|
+
* if (res.deref() != null) { ... }
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* @param fiber
|
|
72
|
+
* @param timeout
|
|
73
|
+
* @param opts
|
|
74
|
+
*/
|
|
75
|
+
export declare const withTimeout: (fiber: MaybeFiber, timeout: number, opts?: Partial<FiberOpts>) => Fiber<Fiber<any>>;
|
|
76
|
+
/**
|
|
77
|
+
* Higher-order fiber which repeatedly executes given `fiber` until its
|
|
78
|
+
* completion, but does so in a time-sliced manner, such that the fiber never
|
|
79
|
+
* consumes more than `maxTime` milliseconds per update cycle.
|
|
80
|
+
*
|
|
81
|
+
* @param fiber
|
|
82
|
+
* @param maxTime
|
|
83
|
+
* @param opts
|
|
84
|
+
*/
|
|
85
|
+
export declare const timeSlice: (body: Fiber | FiberFactory | Generator, maxTime: number, opts?: Partial<FiberOpts>) => Fiber<void>;
|
|
86
|
+
/**
|
|
87
|
+
* Returns a fiber which "blocks" until given predicate function returns true.
|
|
88
|
+
*
|
|
89
|
+
* @remarks
|
|
90
|
+
* See {@link untilState} for stateful version.
|
|
91
|
+
*
|
|
92
|
+
* @param pred
|
|
93
|
+
*/
|
|
94
|
+
export declare function until(pred: Fn0<boolean>): Generator<undefined, void, unknown>;
|
|
95
|
+
/**
|
|
96
|
+
* Stateful version of {@link until}. Takes an arbitrary `state`
|
|
97
|
+
* value/container and returns a fiber which "blocks" until given predicate
|
|
98
|
+
* function returns true. The `state` is passed to the predicate in each
|
|
99
|
+
* iteration.
|
|
100
|
+
*
|
|
101
|
+
* @param state
|
|
102
|
+
* @param pred
|
|
103
|
+
*/
|
|
104
|
+
export declare function untilState<T>(state: T, pred: Predicate<T>): Generator<undefined, void, unknown>;
|
|
105
|
+
/**
|
|
106
|
+
* Returns a fiber which "blocks" until the given `promise` resolves or rejects.
|
|
107
|
+
* In the latter case, the fiber will throw the received error.
|
|
108
|
+
*
|
|
109
|
+
* @remove
|
|
110
|
+
* If the erroring fiber was added directly to a {@link FiberPool}, the error
|
|
111
|
+
* will be logged and the fiber removed. See {@link FiberPool.update} for
|
|
112
|
+
* details.
|
|
113
|
+
*
|
|
114
|
+
* @param promise
|
|
115
|
+
*/
|
|
116
|
+
export declare const untilPromise: <T>(promise: PromiseLike<T>) => Fiber<T>;
|
|
117
|
+
/**
|
|
118
|
+
* Returns fiber which attaches a one-off event handler for event `type` to
|
|
119
|
+
* `target` and then "blocks" until the event occurred.
|
|
120
|
+
*
|
|
121
|
+
* @remarks
|
|
122
|
+
* The event handler will be removed when the fiber terminates. Upon completion,
|
|
123
|
+
* the event will be the fiber's {@link Fiber.value}.
|
|
124
|
+
*
|
|
125
|
+
* @param target
|
|
126
|
+
* @param type
|
|
127
|
+
* @param opts
|
|
128
|
+
*/
|
|
129
|
+
export declare const untilEvent: (target: EventTarget, type: string, opts?: Partial<FiberOpts>) => Fiber<unknown>;
|
|
130
|
+
//# sourceMappingURL=ops.d.ts.map
|