| @@ -0,0 +1,86 | |||
|
|
1 | import tape = require("tape"); | |
|
|
2 | import { delay } from "./TestTraits"; | |
|
|
3 | import { Cancellation } from "@implab/core/Cancellation"; | |
|
|
4 | import { first, isPromise } from "@implab/core/safe"; | |
|
|
5 | ||
|
|
6 | tape("await delay test", async t => { | |
|
|
7 | // schedule delay | |
|
|
8 | let resolved = false; | |
|
|
9 | let res = delay(0).then(() => resolved = true); | |
|
|
10 | ||
|
|
11 | t.false(resolved, "the delay should be async"); | |
|
|
12 | ||
|
|
13 | await res; | |
|
|
14 | t.pass("await delay"); | |
|
|
15 | ||
|
|
16 | // create cancellation token | |
|
|
17 | let cancel: (e?: any) => void; | |
|
|
18 | const ct = new Cancellation(c => cancel = c); | |
|
|
19 | ||
|
|
20 | // schedule delay | |
|
|
21 | resolved = false; | |
|
|
22 | res = delay(0, ct).then(() => resolved = true); | |
|
|
23 | ||
|
|
24 | t.false(resolved, "created delay with ct"); | |
|
|
25 | ||
|
|
26 | // cancel | |
|
|
27 | cancel(); | |
|
|
28 | ||
|
|
29 | try { | |
|
|
30 | await res; | |
|
|
31 | t.fail("the delay should fail when it is cancelled"); | |
|
|
32 | } catch { | |
|
|
33 | t.pass("the delay is cancelled"); | |
|
|
34 | } | |
|
|
35 | ||
|
|
36 | let died = false; | |
|
|
37 | ||
|
|
38 | // try schedule delay after the cancellation is requested | |
|
|
39 | res = delay(0, ct).then(x => true, () => died = true); | |
|
|
40 | ||
|
|
41 | t.false(died, "The delay should be scheduled even if the cancellation is requested"); | |
|
|
42 | ||
|
|
43 | await res; | |
|
|
44 | t.true(died, "the delay should fail when cancelled"); | |
|
|
45 | ||
|
|
46 | t.end(); | |
|
|
47 | }); | |
|
|
48 | ||
|
|
49 | tape("sequemce test", async t => { | |
|
|
50 | const sequence = ["a", "b", "c"]; | |
|
|
51 | const empty = []; | |
|
|
52 | ||
|
|
53 | // synchronous tests | |
|
|
54 | t.equals(first(sequence), "a", "Should return the first element"); | |
|
|
55 | ||
|
|
56 | let v: string; | |
|
|
57 | let e: Error; | |
|
|
58 | first(sequence, x => v = x); | |
|
|
59 | t.equal(v, "a", "The callback should be called for the first element"); | |
|
|
60 | ||
|
|
61 | t.throws(() => { | |
|
|
62 | first(empty); | |
|
|
63 | }, "Should throw when the sequence is empty"); | |
|
|
64 | ||
|
|
65 | t.throws(() => { | |
|
|
66 | first(empty, x => v = x); | |
|
|
67 | }, "Should throw when the sequence is empty"); | |
|
|
68 | ||
|
|
69 | first(empty, null, x => e = x); | |
|
|
70 | t.true(e, "The errorback should be called for the empty sequence"); | |
|
|
71 | ||
|
|
72 | // async tests | |
|
|
73 | const asyncSequence = Promise.resolve(sequence); | |
|
|
74 | const asyncEmptySequence = Promise.resolve(empty); | |
|
|
75 | ||
|
|
76 | const promise = first(asyncSequence); | |
|
|
77 | t.true(isPromise(promise), "Should return promise"); | |
|
|
78 | ||
|
|
79 | v = await promise; | |
|
|
80 | t.equal(v, "a", "Should return the first element"); | |
|
|
81 | ||
|
|
82 | v = await new Promise(resolve => first(asyncSequence, resolve)); | |
|
|
83 | t.equal(v, "a", "The callback should be called for the first element"); | |
|
|
84 | ||
|
|
85 | t.end(); | |
|
|
86 | }); | |
| @@ -65,4 +65,4 | |||
|
|
65 | 65 | |
|
|
66 | 66 | ```shell |
|
|
67 | 67 | ./gradlew test pack -Pjsmodule=commonjs -Ptarget=es2017 |
|
|
68 | ``` No newline at end of file | |
|
|
68 | ``` | |
| @@ -1,16 +1,11 | |||
|
|
1 | 1 | import { ICancellation, IDestroyable } from "./interfaces"; |
|
|
2 | import { argumentNotNull } from "./safe"; | |
|
|
3 | ||
|
|
4 | const destroyed = { | |
|
|
5 | destroy() { | |
|
|
6 | } | |
|
|
7 | }; | |
|
|
2 | import { argumentNotNull, destroyed } from "./safe"; | |
|
|
8 | 3 | |
|
|
9 | 4 | export class Cancellation implements ICancellation { |
|
|
10 | 5 | private _reason: any; |
|
|
11 | private _cbs: Array<(e) => void>; | |
|
|
6 | private _cbs: Array<(e: any) => void>; | |
|
|
12 | 7 | |
|
|
13 | constructor(action: (cancel: (e) => void) => void) { | |
|
|
8 | constructor(action: (cancel: (e?: any) => void) => void) { | |
|
|
14 | 9 | argumentNotNull(action, "action"); |
|
|
15 | 10 | |
|
|
16 | 11 | action(this._cancel.bind(this)); |
| @@ -1,22 +1,17 | |||
|
|
1 | import { IObservable, IDestroyable, ICancellation } from "./interfaces"; | |
|
|
1 | import { IObservable, IDestroyable, ICancellation, IObserver } from "./interfaces"; | |
|
|
2 | 2 | import { Cancellation } from "./Cancellation"; |
|
|
3 | import { argumentNotNull } from "./safe"; | |
|
|
3 | import { argumentNotNull, destroyed } from "./safe"; | |
|
|
4 | 4 | |
|
|
5 | 5 | type Handler<T> = (x: T) => void; |
|
|
6 | 6 | |
|
|
7 | 7 | type Initializer<T> = (notify: Handler<T>, error?: (e: any) => void, complete?: () => void) => void; |
|
|
8 | 8 | |
|
|
9 | // TODO: think about to move this interfaces.ts and make it public | |
|
|
10 | interface IObserver<T> { | |
|
|
11 | next(event: T): void; | |
|
|
9 | const noop = () => { }; | |
|
|
12 | 10 | |
|
|
13 | error(e: any): void; | |
|
|
14 | ||
|
|
15 | complete(): void; | |
|
|
11 | function isObserver(val: any): val is IObserver<any> { | |
|
|
12 | return val && (typeof val.next === "function"); | |
|
|
16 | 13 | } |
|
|
17 | 14 | |
|
|
18 | const noop = () => { }; | |
|
|
19 | ||
|
|
20 | 15 | export class Observable<T> implements IObservable<T> { |
|
|
21 | 16 | private _once = new Array<IObserver<T>>(); |
|
|
22 | 17 | |
| @@ -65,6 +60,35 export class Observable<T> implements IO | |||
|
|
65 | 60 | return observer; |
|
|
66 | 61 | } |
|
|
67 | 62 | |
|
|
63 | subscribe(next: IObserver<T> | Handler<T>, error?: Handler<any>, complete?: () => void): IDestroyable { | |
|
|
64 | if (isObserver(next)) { | |
|
|
65 | const me = this; | |
|
|
66 | const subscription = { | |
|
|
67 | destroy() { | |
|
|
68 | me._removeObserver(next); | |
|
|
69 | } | |
|
|
70 | }; | |
|
|
71 | this._addObserver(next); | |
|
|
72 | return subscription; | |
|
|
73 | } else if (next) { | |
|
|
74 | const observer = { | |
|
|
75 | next, | |
|
|
76 | error, | |
|
|
77 | complete | |
|
|
78 | }; | |
|
|
79 | const me = this; | |
|
|
80 | const subscription = { | |
|
|
81 | destroy() { | |
|
|
82 | me._removeObserver(observer); | |
|
|
83 | } | |
|
|
84 | }; | |
|
|
85 | this._addObserver(observer); | |
|
|
86 | return subscription; | |
|
|
87 | } else { | |
|
|
88 | return destroyed; | |
|
|
89 | } | |
|
|
90 | } | |
|
|
91 | ||
|
|
68 | 92 | private _addObserver(observer: IObserver<T>) { |
|
|
69 | 93 | if (this._complete) { |
|
|
70 | 94 | try { |
| @@ -86,7 +110,7 export class Observable<T> implements IO | |||
|
|
86 | 110 | * |
|
|
87 | 111 | * @param ct a cancellation token |
|
|
88 | 112 | */ |
|
|
89 |
next(ct: ICancellation = Cancellation.none) |
|
|
|
113 | next(ct: ICancellation = Cancellation.none) { | |
|
|
90 | 114 | return new Promise<T>((resolve, reject) => { |
|
|
91 | 115 | const observer: IObserver<T> = { |
|
|
92 | 116 | next: resolve, |
| @@ -62,7 +62,7 export class Configuration { | |||
|
|
62 | 62 | _require: ModuleResolver; |
|
|
63 | 63 | |
|
|
64 | 64 | constructor(container: Container) { |
|
|
65 | argumentNotNull(container, container); | |
|
|
65 | argumentNotNull(container, "container"); | |
|
|
66 | 66 | this._container = container; |
|
|
67 | 67 | this._path = []; |
|
|
68 | 68 | } |
| @@ -91,3 +91,11 export interface IObservable<T> { | |||
|
|
91 | 91 | on(next: (x: T) => void, error?: (e: any) => void, complete?: () => void): IDestroyable; |
|
|
92 | 92 | next(ct?: ICancellation): Promise<T>; |
|
|
93 | 93 | } |
|
|
94 | ||
|
|
95 | export interface IObserver<T> { | |
|
|
96 | next(event: T): void; | |
|
|
97 | ||
|
|
98 | error(e: any): void; | |
|
|
99 | ||
|
|
100 | complete(): void; | |
|
|
101 | } | |
| @@ -1,4 +1,5 | |||
|
|
1 | 1 | import { ICancellable, Constructor } from "./interfaces"; |
|
|
2 | import { Cancellation } from "./Cancellation"; | |
|
|
2 | 3 | |
|
|
3 | 4 | let _nextOid = 0; |
|
|
4 | 5 | const _oid = typeof Symbol === "function" ? |
| @@ -246,6 +247,24 export function delegate<T, K extends ke | |||
|
|
246 | 247 | }; |
|
|
247 | 248 | } |
|
|
248 | 249 | |
|
|
250 | export function delay(timeMs: number, ct = Cancellation.none) { | |
|
|
251 | return new Promise((resolve, reject) => { | |
|
|
252 | if (ct.isRequested()) { | |
|
|
253 | ct.register(reject); | |
|
|
254 | } else { | |
|
|
255 | const h = ct.register(e => { | |
|
|
256 | clearTimeout(id); | |
|
|
257 | reject(e); | |
|
|
258 | // we don't nedd to unregister h, since ct is already disposed | |
|
|
259 | }); | |
|
|
260 | const id = setTimeout(() => { | |
|
|
261 | h.destroy(); | |
|
|
262 | resolve(); | |
|
|
263 | }, timeMs); | |
|
|
264 | } | |
|
|
265 | }); | |
|
|
266 | } | |
|
|
267 | ||
|
|
249 | 268 | /** |
|
|
250 | 269 | * Для каждого элемента массива вызывает указанную функцию и сохраняет |
|
|
251 | 270 | * возвращенное значение в массиве результатов. |
| @@ -381,8 +400,8 export function firstWhere<T>( | |||
|
|
381 | 400 | export function firstWhere<T>( |
|
|
382 | 401 | sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>, |
|
|
383 | 402 | predicate?: (x: T) => boolean, |
|
|
384 |
cb?: (x: T) => |
|
|
|
385 |
err?: (x: Error) => |
|
|
|
403 | cb?: (x: T) => any, | |
|
|
404 | err?: (x: Error) => any | |
|
|
386 | 405 | ) { |
|
|
387 | 406 | if (isPromise(sequence)) { |
|
|
388 | 407 | return sequence.then(res => firstWhere(res, predicate, cb, err)); |
| @@ -394,7 +413,7 export function firstWhere<T>( | |||
|
|
394 | 413 | throw new Error("The sequence is empty"); |
|
|
395 | 414 | } else { |
|
|
396 | 415 | if (!predicate) { |
|
|
397 | return cb ? cb(sequence[0]) : sequence[0]; | |
|
|
416 | return cb ? cb(sequence[0]) && void (0) : sequence[0]; | |
|
|
398 | 417 | } else { |
|
|
399 | 418 | for (let i = 0; i < sequence.length; i++) { |
|
|
400 | 419 | const v = sequence[i]; |
| @@ -426,3 +445,12 export function destroy(d: any) { | |||
|
|
426 | 445 | */ |
|
|
427 | 446 | export function nowait(p: Promise<any>) { |
|
|
428 | 447 | } |
|
|
448 | ||
|
|
449 | /** represents already destroyed object. | |
|
|
450 | */ | |
|
|
451 | export const destroyed = { | |
|
|
452 | /** Calling to this method doesn't affect anything, noop. | |
|
|
453 | */ | |
|
|
454 | destroy() { | |
|
|
455 | } | |
|
|
456 | }; | |
General Comments 0
You need to be logged in to leave comments.
Login now
