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