##// END OF EJS Templates
Rewritten safe::debounce
cin -
r163:4031b379ac68 v1.4.1 default
parent child
Show More
@@ -1,565 +1,565
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 4 let _nextOid = 0;
4 5 const _oid = typeof Symbol === "function" ?
5 6 Symbol("__implab__oid__") :
6 7 "__implab__oid__";
7 8
8 9 function _noop() { }
9 10
10 11 export function oid(instance: null | undefined): undefined;
11 12 export function oid(instance: NonNullable<any>): string;
12 13 export function oid(instance: any): string | undefined {
13 14 if (isNull(instance))
14 15 return undefined;
15 16
16 17 if (_oid in instance)
17 18 return instance[_oid];
18 19 else
19 20 return (instance[_oid] = "oid_" + (++_nextOid));
20 21 }
21 22
22 23 const cancellationNone: ICancellation = {
23 24 isSupported(): boolean {
24 25 return false;
25 26 },
26 27
27 28 throwIfRequested(): void {
28 29 },
29 30
30 31 isRequested(): boolean {
31 32 return false;
32 33 },
33 34
34 35 register(_cb: (e: any) => void): IDestroyable {
35 36 return destroyed;
36 37 }
37 38 };
38 39
39 40 export function keys<T>(arg: T): (Extract<keyof T, string>)[] {
40 41 return isObject(arg) && arg ? Object.keys(arg) as (Extract<keyof T, string>)[] : [];
41 42 }
42 43
43 44 export function isKeyof<T>(k: string, target: T): k is Extract<keyof T, string> {
44 45 return target && typeof target === "object" && k in target;
45 46 }
46 47
47 48 export function argumentNotNull(arg: any, name: string) {
48 49 if (arg === null || arg === undefined)
49 50 throw new Error("The argument " + name + " can't be null or undefined");
50 51 }
51 52
52 53 export function argumentNotEmptyString(arg: any, name: string) {
53 54 if (typeof (arg) !== "string" || !arg.length)
54 55 throw new Error("The argument '" + name + "' must be a not empty string");
55 56 }
56 57
57 58 export function argumentNotEmptyArray(arg: any, name: string) {
58 59 if (!(arg instanceof Array) || !arg.length)
59 60 throw new Error("The argument '" + name + "' must be a not empty array");
60 61 }
61 62
62 63 export function argumentOfType(arg: any, type: Constructor<{}>, name: string) {
63 64 if (!(arg instanceof type))
64 65 throw new Error("The argument '" + name + "' type doesn't match");
65 66 }
66 67
67 68 export function isNull(val: any): val is null | undefined {
68 69 return (val === null || val === undefined);
69 70 }
70 71
71 72 export type primitive = symbol | string | number | boolean | undefined | null;
72 73
73 74 export function isPrimitive(val: any): val is primitive {
74 75 return (val === null || val === undefined || typeof (val) === "string" ||
75 76 typeof (val) === "number" || typeof (val) === "boolean");
76 77 }
77 78
78 79 export function isObject<T>(value: T): value is Exclude<T & object, primitive> {
79 80 return !!(value && typeof value === "object");
80 81 }
81 82
82 83 export function isInteger(val: any): val is number {
83 84 return parseInt(val, 10) === val;
84 85 }
85 86
86 87 export function isNumber(val: any): val is number {
87 88 return parseFloat(val) === val;
88 89 }
89 90
90 91 export function isString(val: any): val is string {
91 92 return typeof (val) === "string";
92 93 }
93 94
94 95 export function isPromise<T = any>(val: any): val is PromiseLike<T> {
95 96 return !!(val && typeof val.then === "function");
96 97 }
97 98
98 99 export function isCancellable(val: any): val is ICancellable {
99 100 return !!(val && typeof val.cancel === "function");
100 101 }
101 102
102 103 export function isNullOrEmptyString(val: any): val is ("" | null | undefined) {
103 104 return (val === null || val === undefined ||
104 105 ((typeof (val) === "string" || val instanceof String) && val.length === 0));
105 106 }
106 107
107 108 export function isNotEmptyArray<T = any>(arg: any): arg is T[] {
108 109 return (arg instanceof Array && arg.length > 0);
109 110 }
110 111
111 112 function _isStrictMode(this: any) {
112 113 return !this;
113 114 }
114 115
115 116 function _getNonStrictGlobal(this: any) {
116 117 return this;
117 118 }
118 119
119 120 export function getGlobal() {
120 121 // in es3 we can't use indirect call to eval, since it will
121 122 // be executed in the current call context.
122 123 if (!_isStrictMode()) {
123 124 return _getNonStrictGlobal();
124 125 } else {
125 126 // tslint:disable-next-line:no-eval
126 127 return eval.call(null, "this");
127 128 }
128 129 }
129 130
130 131 export function get(member: string, context?: object) {
131 132 argumentNotEmptyString(member, "member");
132 133 let that = context || getGlobal();
133 134 const parts = member.split(".");
134 135 for (const m of parts) {
135 136 if (!m)
136 137 continue;
137 138 if (isNull(that = that[m]))
138 139 break;
139 140 }
140 141 return that;
141 142 }
142 143
143 144 /**
144 145 * ВыполняСт ΠΌΠ΅Ρ‚ΠΎΠ΄ для ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ элСмСнта массива, останавливаСтся, ΠΊΠΎΠ³Π΄Π°
145 146 * Π»ΠΈΠ±ΠΎ достигнут ΠΊΠΎΠ½Π΅Ρ† массива, Π»ΠΈΠ±ΠΎ функция <c>cb</c> Π²Π΅Ρ€Π½ΡƒΠ»Π°
146 147 * Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅.
147 148 *
148 149 * @param {Array | Object} obj массив элСмСнтов для просмотра
149 150 * @param {Function} cb функция, вызываСмая для ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ элСмСнта
150 151 * @param {Object} thisArg Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅, ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ΅ Π±ΡƒΠ΄Π΅Ρ‚ ΠΏΠ΅Ρ€Π΅Π΄Π°Π½ΠΎ Π² качСствС
151 152 * <c>this</c> Π² <c>cb</c>.
152 153 * @returns {void}
153 154 */
154 155 export function each<T>(obj: T, cb: <X extends Extract<keyof T, string>>(v: NonNullable<T[X]>, k: X) => void): void;
155 156 export function each<T>(array: T[], cb: (v: T, i: number) => void): void;
156 157 export function each(obj: any, cb: any, thisArg?: any): any;
157 158 export function each(obj: any, cb: any, thisArg?: any) {
158 159 argumentNotNull(cb, "cb");
159 160 if (obj instanceof Array) {
160 161 let v: any;
161 162 for (let i = 0; i < obj.length; i++) {
162 163 v = obj[i];
163 164 if (v !== undefined)
164 165 cb.call(thisArg, v, i);
165 166 }
166 167 } else {
167 168 Object.keys(obj).forEach(k => obj[k] !== undefined && cb.call(thisArg, obj[k], k));
168 169 }
169 170 }
170 171
171 172 /** Copies property values from a source object to the destination and returns
172 173 * the destination object.
173 174 *
174 175 * @param dest The destination object into which properties from the source
175 176 * object will be copied.
176 177 * @param source The source of values which will be copied to the destination
177 178 * object.
178 179 * @param template An optional parameter specifies which properties should be
179 180 * copied from the source and how to map them to the destination. If the
180 181 * template is an array it contains the list of property names to copy from the
181 182 * source to the destination. In case of object the templates contains the map
182 183 * where keys are property names in the source and the values are property
183 184 * names in the destination object. If the template isn't specified then the
184 185 * own properties of the source are entirely copied to the destination.
185 186 *
186 187 */
187 188 export function mixin<T extends object, S extends object>(dest: T, source: S, template?: (keyof S)[]): T & S;
188 189 export function mixin<T extends object, S extends object, R extends object = T>(dest: T, source: S, template: { [p in keyof S]?: keyof R; }): T & R;
189 190 export function mixin<T extends object, S extends object>(dest: T, source: S, template?: any): any {
190 191 argumentNotNull(dest, "dest");
191 192 const _res: any = dest as any;
192 193
193 194 if (isPrimitive(source))
194 195 return _res;
195 196
196 197 if (template instanceof Array) {
197 198 template.forEach(p => {
198 199 if (isKeyof(p, source))
199 200 _res[p] = source[p];
200 201 });
201 202 } else if (template) {
202 203 keys(source).forEach(p => {
203 204 if (isKeyof(p, template))
204 205 _res[template[p]] = source[p];
205 206 });
206 207 } else {
207 208 keys(source).forEach(p => _res[p] = source[p]);
208 209 }
209 210
210 211 return _res;
211 212 }
212 213
213 214 /** Wraps the specified function to emulate an asynchronous execution.
214 215 * @param{Object} thisArg [Optional] Object which will be passed as 'this' to the function.
215 216 * @param{Function|String} fn [Required] Function wich will be wrapped.
216 217 */
217 218 export function async<T, F extends (...args: any[]) => T | PromiseLike<T>>(
218 219 fn: F,
219 220 thisArg?: ThisParameterType<F>
220 221 ): (...args: Parameters<F>) => PromiseLike<T>;
221 222 export function async<T, M extends string, O extends { [m in M]?: (...args: any[]) => T | PromiseLike<T> }>(
222 223 fn: M,
223 224 thisArg: O
224 225 ): (...args: Parameters<NonNullable<O[M]>>) => PromiseLike<T>;
225 226 export function async(_fn: any, thisArg: any): (...args: any[]) => PromiseLike<any> {
226 227 let fn = _fn;
227 228
228 229 if (arguments.length === 2 && !(fn instanceof Function))
229 230 fn = thisArg[fn];
230 231
231 232 if (fn == null)
232 233 throw new Error("The function must be specified");
233 234
234 235 function wrapresult(x: any, e?: any): PromiseLike<any> {
235 236 if (e) {
236 237 return {
237 238 then(cb, eb) {
238 239 try {
239 240 return eb ? wrapresult(eb(e)) : this;
240 241 } catch (e2) {
241 242 return wrapresult(null, e2);
242 243 }
243 244 }
244 245 };
245 246 } else {
246 247 if (x && x.then)
247 248 return x;
248 249 return {
249 250 then(cb) {
250 251 try {
251 252 return cb ? wrapresult(cb(x)) : this;
252 253 } catch (e2) {
253 254 return wrapresult(e2);
254 255 }
255 256 }
256 257 };
257 258 }
258 259 }
259 260
260 261 return (...args) => {
261 262 try {
262 263 return wrapresult(fn.apply(thisArg, args));
263 264 } catch (e) {
264 265 return wrapresult(null, e);
265 266 }
266 267 };
267 268 }
268 269
269 270 export function delegate<T extends object, F extends (this: T, ...args: any[]) => any>(
270 271 target: T,
271 272 method: F
272 273 ): OmitThisParameter<F>;
273 274 export function delegate<M extends string, T extends { [m in M]?: (...args: any[]) => any; }>(
274 275 target: T,
275 276 method: M
276 277 ): OmitThisParameter<T[M]>;
277 278 export function delegate(target: any, _method: any): (...args: any[]) => any {
278 279 let method: any;
279 280 if (!(_method instanceof Function)) {
280 281 argumentNotNull(target, "target");
281 282 method = target[_method];
282 283 if (!(method instanceof Function))
283 284 throw new Error("'method' argument must be a Function or a method name");
284 285 } else {
285 286 method = _method;
286 287 }
287 288
288 289 return (...args) => {
289 290 return method.apply(target, args);
290 291 };
291 292 }
292 293
293 294 /** Returns promise which will be resolved after the specified amount of time.
294 295 *
295 296 * @param timeMs The delay before the promise will be resolved in milliseconds.
296 297 * @param ct Optional. A cancellation token for the operation.
297 298 */
298 299 export function delay(timeMs: number, ct = cancellationNone) {
299 300 ct.throwIfRequested();
300 301 return new Promise<void>((resolve, reject) => {
301 302 const h = ct.register(e => {
302 303 clearTimeout(id);
303 304 reject(e);
304 305 // we don't nedd to unregister h, since ct is already disposed
305 306 });
306 307 const id = setTimeout(() => {
307 308 h.destroy();
308 309 resolve();
309 310 }, timeMs);
310 311
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 328 let cancel: (e?: any) => void = _noop;
316 329
317 const fn = function executedFunction(this: This, ...args: T) {
318 return new Promise<R>((resolve, reject) => {
330 let pending = Promise.resolve();
319 331
320 // used to cleanup currently allocated resources
321 const _cleanup = () => {
322 cancel = _noop;
323 clearTimeout(handle);
324 };
332 const fn = async function executedFunction(this: This, ...args: T) {
333 cancel();
334 const ct = new Cancellation(_cancel => cancel = _cancel);
325 335
326 // used in case of cancellation of the current operation
327 const _cancel = (e: any) => {
328 _cleanup();
329 reject(e);
330 };
331
332 // performs actual work
333 const _later = () => {
334 _cleanup();
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 });
336 await pending;
337 let resolve = _noop;
338 pending = new Promise(_resolve => resolve = _resolve);
339 try {
340 await delay(wait, ct);
341 return func(ct).apply(this, args);
342 } finally {
343 resolve();
344 }
345 345 };
346 346
347 347 fn.cancel = (e?: any) => cancel(e);
348 348
349 349 return fn;
350 350 }
351 351
352 352 /** Returns resolved promise, awaiting this method will cause the asynchronous
353 353 * completion of the rest of the code.
354 354 */
355 355 export function fork() {
356 356 return Promise.resolve();
357 357 }
358 358
359 359 /** Always throws Error, can be used as a stub for the methods which should be
360 360 * assigned later and are required to be not null.
361 361 */
362 362 export function notImplemented(): never {
363 363 throw new Error("Not implemented");
364 364 }
365 365 /**
366 366 * Iterates over the specified array of items and calls the callback `cb`, if
367 367 * the result of the callback is a promise the next item from the array will be
368 368 * proceeded after the promise is resolved.
369 369 *
370 370 */
371 371 export function pmap<T, T2>(
372 372 items: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
373 373 cb: (item: T, i: number) => T2 | PromiseLike<T2>
374 374 ): T2[] | PromiseLike<T2[]> {
375 375 argumentNotNull(cb, "cb");
376 376
377 377 if (isPromise(items)) {
378 378 return items.then(data => pmap(data, cb));
379 379 } else {
380 380
381 381 if (isNull(items) || !items.length)
382 382 return [];
383 383
384 384 let i = 0;
385 385 const result = new Array<T2>();
386 386
387 387 const next = (): any => {
388 388 while (i < items.length) {
389 389 const r = cb(items[i], i);
390 390 const ri = i;
391 391 i++;
392 392 if (isPromise(r)) {
393 393 return r.then(x => {
394 394 result[ri] = x;
395 395 return next();
396 396 });
397 397 } else {
398 398 result[ri] = r;
399 399 }
400 400 }
401 401 return result;
402 402 };
403 403
404 404 return next();
405 405 }
406 406 }
407 407
408 408 export function pfor<T>(
409 409 items: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
410 410 cb: (item: T, i: number) => any
411 411 ): void | PromiseLike<void> {
412 412 argumentNotNull(cb, "cb");
413 413
414 414 if (isPromise(items)) {
415 415 return items.then(data => pfor(data, cb));
416 416 } else {
417 417 if (isNull(items) || !items.length)
418 418 return;
419 419
420 420 let i = 0;
421 421
422 422 const next = (): any => {
423 423 while (i < items.length) {
424 424 const r = cb(items[i], i);
425 425 i++;
426 426 if (isPromise(r))
427 427 return r.then(next);
428 428 }
429 429 };
430 430
431 431 return next();
432 432 }
433 433 }
434 434
435 435 export function first<T>(sequence: ArrayLike<T>): T;
436 436 export function first<T>(sequence: PromiseLike<ArrayLike<T>>): PromiseLike<T>;
437 437 export function first<T>(
438 438 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
439 439 cb?: (x: T) => void,
440 440 err?: (x: Error) => void
441 441 ): void;
442 442 /**
443 443 * Π’Ρ‹Π±ΠΈΡ€Π°Π΅Ρ‚ ΠΏΠ΅Ρ€Π²Ρ‹ΠΉ элСмСнт ΠΈΠ· ΠΏΠΎΡΠ»Π΅Π΄ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ, ΠΈΠ»ΠΈ обСщания, Ссли Π²
444 444 * качСствС ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Π° ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ ΠΎΠ±Π΅Ρ‰Π°Π½ΠΈΠ΅, ΠΎΠ½ΠΎ Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π²Π΅Ρ€Π½ΡƒΡ‚ΡŒ массив.
445 445 *
446 446 * @param {Function} cb ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Π°, Π΅ΠΌΡƒ Π±ΡƒΠ΄Π΅Ρ‚ ΠΏΠ΅Ρ€Π΅Π΄Π°Π½ ΠΏΠ΅Ρ€Π²Ρ‹ΠΉ
447 447 * элСмСнт ΠΏΠΎΡΠ»Π΅Π΄ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ Π² случаС успСха
448 448 * @param {Function} err ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ ΠΈΡΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΡ, Ссли массив пустой, Π»ΠΈΠ±ΠΎ
449 449 * нС массив
450 450 *
451 451 * @remarks Если Π½Π΅ ΡƒΠΊΠ°Π·Π°Π½Ρ‹ Π½ΠΈ cb Π½ΠΈ err, Ρ‚ΠΎΠ³Π΄Π° функция Π²Π΅Ρ€Π½Π΅Ρ‚ Π»ΠΈΠ±ΠΎ
452 452 * ΠΎΠ±Π΅Ρ‰Π°Π½ΠΈΠ΅, Π»ΠΈΠ±ΠΎ ΠΏΠ΅Ρ€Π²Ρ‹ΠΉ элСмСнт.
453 453 * @async
454 454 */
455 455 export function first<T>(
456 456 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
457 457 cb?: (x: T) => void,
458 458 err?: (x: Error) => void
459 459 ) {
460 460 if (isPromise(sequence)) {
461 461 return sequence.then(res => first(res, cb as any /* force to pass undefined cb */, err));
462 462 } else if (sequence && "length" in sequence) {
463 463 if (sequence.length === 0) {
464 464 if (err)
465 465 return err(new Error("The sequence is empty"));
466 466 else
467 467 throw new Error("The sequence is empty");
468 468 } else if (cb) {
469 469 return cb(sequence[0]);
470 470 } else {
471 471 return sequence[0];
472 472 }
473 473 } else {
474 474 if (err)
475 475 return err(new Error("The sequence is required"));
476 476 else
477 477 throw new Error("The sequence is required");
478 478 }
479 479 }
480 480
481 481 export function firstWhere<T>(
482 482 sequence: ArrayLike<T>,
483 483 predicate: (x: T) => boolean
484 484 ): T;
485 485 export function firstWhere<T>(
486 486 sequence: PromiseLike<ArrayLike<T>>,
487 487 predicate: (x: T) => boolean
488 488 ): PromiseLike<T>;
489 489 export function firstWhere<T>(
490 490 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
491 491 predicate: (x: T) => boolean,
492 492 cb: (x: T) => void,
493 493 err?: (x: Error) => void
494 494 ): void;
495 495
496 496 export function firstWhere<T>(
497 497 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
498 498 predicate?: (x: T) => boolean,
499 499 cb?: (x: T) => any,
500 500 err?: (x: Error) => any
501 501 ) {
502 502 if (isPromise(sequence)) {
503 503 return sequence.then(res => firstWhere(
504 504 res,
505 505 predicate as any /* force to pass undefined predicate */,
506 506 cb as any /* force to pass undefined cb */,
507 507 err)
508 508 );
509 509 } else if (sequence && "length" in sequence) {
510 510 if (sequence.length === 0) {
511 511 if (err)
512 512 err(new Error("The sequence is empty"));
513 513 else
514 514 throw new Error("The sequence is empty");
515 515 } else {
516 516 if (!predicate) {
517 517 return cb ? cb(sequence[0]) && void (0) : sequence[0];
518 518 } else {
519 519 for (let i = 0; i < sequence.length; i++) {
520 520 const v = sequence[i];
521 521 if (predicate(v))
522 522 return cb ? cb(v) : v;
523 523 }
524 524 if (err)
525 525 err(new Error("The sequence doesn't contain matching items"));
526 526 else
527 527 throw new Error("The sequence doesn't contain matching items");
528 528 }
529 529 }
530 530 } else {
531 531 if (err)
532 532 err(new Error("The sequence is required"));
533 533 else
534 534 throw new Error("The sequence is required");
535 535 }
536 536 }
537 537
538 538 export function isDestroyable(d: any): d is IDestroyable {
539 539 return !!(d && typeof d.destroy === "function");
540 540 }
541 541
542 542 export function isRemovable(value: any): value is IRemovable {
543 543 return !!(value && typeof value.remove === "function");
544 544 }
545 545
546 546 export function destroy(d: any) {
547 547 if (isDestroyable(d))
548 548 d.destroy();
549 549 }
550 550
551 551 /**
552 552 * Used to mark that the async operation isn't awaited intentionally.
553 553 * @param p The promise which represents the async operation.
554 554 */
555 555 export function nowait(p: Promise<any>) {
556 556 }
557 557
558 558 /** represents already destroyed object.
559 559 */
560 560 export const destroyed = {
561 561 /** Calling to this method doesn't affect anything, noop.
562 562 */
563 563 destroy() {
564 564 }
565 565 };
@@ -1,125 +1,127
1 1 import { test } from "./TestTraits";
2 2 import { Container } from "../di/Container";
3 3 import { ReferenceDescriptor } from "../di/ReferenceDescriptor";
4 4 import { AggregateDescriptor } from "../di/AggregateDescriptor";
5 5 import { ValueDescriptor } from "../di/ValueDescriptor";
6 6 import { Foo } from "../mock/Foo";
7 7 import { Bar } from "../mock/Bar";
8 8 import { isNull } from "../safe";
9 9 import { Box } from "../mock/Box";
10 10
11 11 test("Container register/resolve tests", async t => {
12 12 const container = new Container<{
13 13 "bla-bla": string;
14 14 "connection": string;
15 15 "dbParams": {
16 16 timeout: number;
17 17 connection: string;
18 18 }
19 19 }>();
20 20
21 21 const connection1 = "db://localhost";
22 22
23 23 t.throws(
24 24 () => container.register("bla-bla", "bla-bla" as any),
25 25 "Do not allow to register anything other than descriptors"
26 26 );
27 27
28 28 t.doesNotThrow(
29 29 () => container.register("connection", new ValueDescriptor(connection1)),
30 30 "register ValueDescriptor"
31 31 );
32 32
33 33 t.equals(container.resolve("connection"), connection1, "resolve string value");
34 34
35 35 t.doesNotThrow(
36 36 () => container.register(
37 37 "dbParams",
38 38 new AggregateDescriptor({
39 39 timeout: 10,
40 40 connection: new ReferenceDescriptor({ name: "connection" })
41 41 })
42 42 ),
43 43 "register AggregateDescriptor"
44 44 );
45 45
46 46 const dbParams = container.resolve("dbParams");
47 47 t.equals(dbParams.connection, connection1, "should get string value 'dbParams.connection'");
48 48 });
49 49
50 50 test("Container configure/resolve tests", async t => {
51 51
52 52 const container = new Container<{
53 53 foo: Foo;
54 54 box: Bar;
55 55 bar: Bar;
56 56 db: any;
57 57 }>();
58 58
59 59 await container.configure({
60 60 foo: {
61 61 $type: Foo
62 62 },
63 63
64 64 box: {
65 65 $type: Bar,
66 66 params: {
67 67 $dependency: "foo"
68 68 }
69 69 },
70 70
71 71 bar: {
72 72 $type: Bar,
73 73 params: [{
74 74 db: {
75 75 provider: {
76 76 $dependency: "db"
77 77 }
78 78 }
79 79 }]
80 80 }
81 81 });
82 82 t.pass("should configure from js object");
83 83
84 84 const f1 = container.resolve("foo");
85 85
86 86 t.assert(!isNull(f1), "foo should be not null");
87 87
88 88 t.throws(() => container.resolve("bar"), "should not resolve dependency 'db'");
89 89
90 90 });
91 91
92 92 test("Load configuration from module", async t => {
93 93 const container = new Container();
94 94
95 95 await container.configure("../mock/config1", { contextRequire: require });
96 96 t.pass("The configuration should load");
97 97
98 98 const f1 = container.resolve("foo");
99 99
100 100 t.assert(!isNull(f1), "foo should be not null");
101 101
102 102 const b1 = container.resolve("bar") as Bar;
103 103
104 104 t.assert(!isNull(b1), "bar should not be null");
105 105 t.assert(!isNull(b1._v), "bar.foo should not be null");
106 106
107 107 });
108 108
109 109 test("Optional dependency with child container", async t => {
110 110 const container = new Container<{
111 111 foo?: Foo;
112 box: Box<Foo>;
112 box: Box<Bar>;
113 bar: Bar;
113 114 }>();
114 115 await container.fluent({
115 box: it => it.factory($ => new Box($("foo")))
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 120 const child = await container.createChildContainer()
119 121 .fluent({
120 122 foo: it => it.factory(() => new Foo())
121 })
123 });
122 124
123 125 const box = child.resolve("box");
124 126 t.assert(!isNull(box.getValue()), "'foo' dependency is declared in child container");
125 }); No newline at end of file
127 });
@@ -1,156 +1,156
1 1 import { Cancellation } from "../Cancellation";
2 2 import { ICancellation } from "../interfaces";
3 3 import { first, isPromise, firstWhere, delay, nowait, notImplemented, debounce, fork } from "../safe";
4 4 import { test } from "./TestTraits";
5 5
6 6 test("await delay test", async t => {
7 7 // schedule delay
8 8 let resolved = false;
9 9 let res = delay(0).then(() => resolved = true);
10 10
11 11 t.false(resolved, "the delay should be async");
12 12
13 13 await res;
14 14 t.pass("await delay");
15 15
16 16 // create cancellation token
17 17 let cancel: (e?: any) => void = notImplemented;
18 18 const ct = new Cancellation(c => cancel = c);
19 19
20 20 // schedule delay
21 21 resolved = false;
22 22 res = delay(0, ct).then(() => resolved = true);
23 23
24 24 t.false(resolved, "created delay with ct");
25 25
26 26 // cancel
27 27 cancel();
28 28
29 29 try {
30 30 await res;
31 31 t.fail("the delay should fail when it is cancelled");
32 32 } catch {
33 33 t.pass("the delay is cancelled");
34 34 }
35 35
36 36 t.throws(() => {
37 37 // try schedule delay after the cancellation is requested
38 38 nowait(delay(0, ct));
39 39 }, "Should throw if cancelled before start");
40 40 });
41 41
42 42 test("sequemce test", async t => {
43 43 const sequence = ["a", "b", "c"];
44 44 const empty: string[] = [];
45 45
46 46 // synchronous tests
47 47 t.equals(first(sequence), "a", "Should return the first element");
48 48 t.equals(firstWhere(sequence, x => x === "b"), "b", "Should get the second element");
49 49
50 50 let v: string | undefined;
51 51 let e: Error | undefined;
52 52 first(sequence, x => v = x);
53 53 t.equal(v, "a", "The callback should be called for the first element");
54 54 firstWhere(sequence, x => x === "b", x => v = x);
55 55 t.equal(v, "b", "The callback should be called for the second element");
56 56
57 57 t.throws(() => {
58 58 first(empty);
59 59 }, "Should throw when the sequence is empty");
60 60
61 61 t.throws(() => {
62 62 firstWhere(empty, x => x === "b");
63 63 }, "Should throw when the sequence is empty");
64 64
65 65 t.throws(() => {
66 66 first(empty, x => v = x);
67 67 }, "Should throw when the sequence is empty");
68 68
69 69 t.throws(() => {
70 70 firstWhere(empty, x => x === "b", x => v = x);
71 71 }, "Should throw when the sequence is empty");
72 72
73 73 t.throws(() => {
74 74 firstWhere(sequence, x => x === "z");
75 75 }, "Should throw when the element isn't found");
76 76
77 77 t.throws(() => {
78 78 firstWhere(sequence, x => x === "z", x => v = x);
79 79 }, "Should throw when the element isn't found");
80 80
81 81 first(empty, undefined, x => e = x);
82 82 t.true(e, "The errorback should be called for the empty sequence");
83 83
84 84 // async tests
85 85 const asyncSequence = Promise.resolve(sequence);
86 86 const asyncEmptySequence = Promise.resolve(empty);
87 87
88 88 const promise = first(asyncSequence);
89 89 t.true(isPromise(promise), "Should return promise");
90 90
91 91 v = await promise;
92 92 t.equal(v, "a", "Should return the first element");
93 93
94 94 v = await new Promise(resolve => first(asyncSequence, resolve));
95 95 t.equal(v, "a", "The callback should be called for the first element");
96 96 });
97 97
98 98 test("debounce tests", async (t, trace) => {
99 99 let count = 0;
100 100 let rejected = 0;
101 101 function increment(step: number = 1) {
102 102 count += step;
103 103 return count;
104 104 }
105 105
106 const f = debounce(increment, 100);
106 const f = debounce(() => increment, 100);
107 107 f().then(undefined, () => rejected++);
108 108 f().then(undefined, () => rejected++);
109 109
110 110 await f(1);
111 111
112 112 t.equal(rejected, 2, "Previous operations should be rejected");
113 113 t.equal(count, 1, "The operation should run once");
114 114
115 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 117 100
118 118 );
119 119
120 120 acc(1, 2, 3).catch(() => { });
121 121 const result = acc(1, 2, 3);
122 122 acc.cancel();
123 123
124 124 try {
125 125 await result;
126 126 t.notOk("fn.cancel() should make current operation to throw an exception");
127 127 } catch {
128 128 t.ok("fn.cancel() should make current operation to throw an exception");
129 129 }
130 130
131 131 t.equal(count, 1, "fn.cancel() The operation should not run");
132 132
133 133 acc.cancel();
134 134 await acc(1, 2);
135 135 t.equal(count, 4, "The variable arguments list shoud be handled correctly");
136 136
137 137 // create cancellation token
138 138 let cancel: (e?: any) => void = notImplemented;
139 139 const ct = new Cancellation(c => cancel = c);
140 140
141 const d = debounce(async () => {
142 ct.throwIfRequested();
141 const d = debounce(() => async (_ct: ICancellation) => {
142 _ct.throwIfRequested();
143 143 trace.debug("do async increment");
144 144 await fork();
145 145 count++;
146 146 return count;
147 147 }, 0);
148 148
149 const p = d().then(undefined, () => rejected++);
149 const p = d(ct).then(undefined, () => rejected++);
150 150 cancel();
151 151 await p;
152 152
153 153 t.equal(count, 4, "Cancellation token should prevent the function execution");
154 154 t.equal(rejected, 3, "Cancellation token should reject operation");
155 155
156 156 });
General Comments 0
You need to be logged in to leave comments. Login now