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