| @@ -1,4 +1,5 | |||||
| 1 | import { ICancellable, Constructor, IDestroyable, ICancellation, IRemovable } from "./interfaces"; |
|
1 | import { Cancellation } from "./Cancellation"; | |
|
|
2 | import { ICancellable, Constructor, IDestroyable, ICancellation, IRemovable, PromiseOrValue } from "./interfaces"; | |||
| 2 |
|
3 | |||
| 3 | let _nextOid = 0; |
|
4 | let _nextOid = 0; | |
| 4 | const _oid = typeof Symbol === "function" ? |
|
5 | const _oid = typeof Symbol === "function" ? | |
| @@ -311,37 +312,36 export function delay(timeMs: number, ct | |||||
| 311 | }); |
|
312 | }); | |
| 312 | } |
|
313 | } | |
| 313 |
|
314 | |||
| 314 | export function debounce<T extends any[], R, This>(func: (this: This, ...args: T) => R | PromiseLike<R>, wait: number) { |
|
315 | /** | |
|
|
316 | * Wraps the specified function to delay its execution by the specified timeout. | |||
|
|
317 | * If the wrapped function was called multiple times before the timeout has elapsed, | |||
|
|
318 | * the target function will be executed only once. | |||
|
|
319 | * Each call to the wrapped function will return a promise is the timeout hasn't | |||
|
|
320 | * elapsed the subsequent calls to the wrapped function will reject previous promises. | |||
|
|
321 | * | |||
|
|
322 | * @param func function that accepts a cancellation token as a single | |||
|
|
323 | * parameter and returns the callback which will be invoked. | |||
|
|
324 | * @param wait A timeout to wait before the callback should be invoked | |||
|
|
325 | * @returns The function withe the same parameters as the original callback. | |||
|
|
326 | */ | |||
|
|
327 | export function debounce<T extends any[], R, This>(func: (ct: ICancellation) => ((this: This, ...args: T) => PromiseOrValue<R>), wait: number) { | |||
| 315 | let cancel: (e?: any) => void = _noop; |
|
328 | let cancel: (e?: any) => void = _noop; | |
| 316 |
|
329 | |||
| 317 | const fn = function executedFunction(this: This, ...args: T) { |
|
330 | let pending = Promise.resolve(); | |
| 318 | return new Promise<R>((resolve, reject) => { |
|
|||
| 319 |
|
331 | |||
| 320 | // used to cleanup currently allocated resources |
|
332 | const fn = async function executedFunction(this: This, ...args: T) { | |
| 321 | const _cleanup = () => { |
|
333 | cancel(); | |
| 322 | cancel = _noop; |
|
334 | const ct = new Cancellation(_cancel => cancel = _cancel); | |
| 323 | clearTimeout(handle); |
|
|||
| 324 | }; |
|
|||
| 325 |
|
335 | |||
| 326 | // used in case of cancellation of the current operation |
|
336 | await pending; | |
| 327 | const _cancel = (e: any) => { |
|
337 | let resolve = _noop; | |
| 328 | _cleanup(); |
|
338 | pending = new Promise(_resolve => resolve = _resolve); | |
| 329 | reject(e); |
|
339 | try { | |
| 330 | }; |
|
340 | await delay(wait, ct); | |
| 331 |
|
341 | return func(ct).apply(this, args); | ||
| 332 | // performs actual work |
|
342 | } finally { | |
| 333 |
|
|
343 | resolve(); | |
| 334 | _cleanup(); |
|
344 | } | |
| 335 | resolve(func.apply(this, args)); |
|
|||
| 336 | }; |
|
|||
| 337 |
|
||||
| 338 | // cancel previously queued operation |
|
|||
| 339 | if (cancel !== _noop) |
|
|||
| 340 | cancel(new Error("Operation cancelled due to debouncing")); |
|
|||
| 341 | cancel = _cancel; |
|
|||
| 342 |
|
||||
| 343 | const handle = setTimeout(_later, wait); |
|
|||
| 344 | }); |
|
|||
| 345 | }; |
|
345 | }; | |
| 346 |
|
346 | |||
| 347 | fn.cancel = (e?: any) => cancel(e); |
|
347 | fn.cancel = (e?: any) => cancel(e); | |
| @@ -109,17 +109,19 test("Load configuration from module", a | |||||
| 109 | test("Optional dependency with child container", async t => { |
|
109 | test("Optional dependency with child container", async t => { | |
| 110 | const container = new Container<{ |
|
110 | const container = new Container<{ | |
| 111 | foo?: Foo; |
|
111 | foo?: Foo; | |
| 112 |
box: Box< |
|
112 | box: Box<Bar>; | |
|
|
113 | bar: Bar; | |||
| 113 | }>(); |
|
114 | }>(); | |
| 114 | await container.fluent({ |
|
115 | await container.fluent({ | |
| 115 |
box: it => it.factory($ => new Box($(" |
|
116 | box: it => it.factory($ => new Box($("bar"))), | |
|
|
117 | bar: it => it.factory($ => new Bar({ host: "local", foo: $("foo") }, "bar")) | |||
| 116 | }); |
|
118 | }); | |
| 117 |
|
119 | |||
| 118 | const child = await container.createChildContainer() |
|
120 | const child = await container.createChildContainer() | |
| 119 | .fluent({ |
|
121 | .fluent({ | |
| 120 | foo: it => it.factory(() => new Foo()) |
|
122 | foo: it => it.factory(() => new Foo()) | |
| 121 | }) |
|
123 | }); | |
| 122 |
|
124 | |||
| 123 | const box = child.resolve("box"); |
|
125 | const box = child.resolve("box"); | |
| 124 | t.assert(!isNull(box.getValue()), "'foo' dependency is declared in child container"); |
|
126 | t.assert(!isNull(box.getValue()), "'foo' dependency is declared in child container"); | |
| 125 | }); No newline at end of file |
|
127 | }); | |
| @@ -103,7 +103,7 test("debounce tests", async (t, trace) | |||||
| 103 | return count; |
|
103 | return count; | |
| 104 | } |
|
104 | } | |
| 105 |
|
105 | |||
| 106 | const f = debounce(increment, 100); |
|
106 | const f = debounce(() => increment, 100); | |
| 107 | f().then(undefined, () => rejected++); |
|
107 | f().then(undefined, () => rejected++); | |
| 108 | f().then(undefined, () => rejected++); |
|
108 | f().then(undefined, () => rejected++); | |
| 109 |
|
109 | |||
| @@ -113,7 +113,7 test("debounce tests", async (t, trace) | |||||
| 113 | t.equal(count, 1, "The operation should run once"); |
|
113 | t.equal(count, 1, "The operation should run once"); | |
| 114 |
|
114 | |||
| 115 | const acc = debounce( |
|
115 | const acc = debounce( | |
| 116 | (...values: number[]) => count = values.reduce((a, v) => v + a, count), |
|
116 | () => (...values: number[]) => count = values.reduce((a, v) => v + a, count), | |
| 117 | 100 |
|
117 | 100 | |
| 118 | ); |
|
118 | ); | |
| 119 |
|
119 | |||
| @@ -138,15 +138,15 test("debounce tests", async (t, trace) | |||||
| 138 | let cancel: (e?: any) => void = notImplemented; |
|
138 | let cancel: (e?: any) => void = notImplemented; | |
| 139 | const ct = new Cancellation(c => cancel = c); |
|
139 | const ct = new Cancellation(c => cancel = c); | |
| 140 |
|
140 | |||
| 141 | const d = debounce(async () => { |
|
141 | const d = debounce(() => async (_ct: ICancellation) => { | |
| 142 | ct.throwIfRequested(); |
|
142 | _ct.throwIfRequested(); | |
| 143 | trace.debug("do async increment"); |
|
143 | trace.debug("do async increment"); | |
| 144 | await fork(); |
|
144 | await fork(); | |
| 145 | count++; |
|
145 | count++; | |
| 146 | return count; |
|
146 | return count; | |
| 147 | }, 0); |
|
147 | }, 0); | |
| 148 |
|
148 | |||
| 149 | const p = d().then(undefined, () => rejected++); |
|
149 | const p = d(ct).then(undefined, () => rejected++); | |
| 150 | cancel(); |
|
150 | cancel(); | |
| 151 | await p; |
|
151 | await p; | |
| 152 |
|
152 | |||
General Comments 0
You need to be logged in to leave comments.
Login now
