##// END OF EJS Templates
Added safe.delay...
cin -
r76:a193ba786ffc default
parent child
Show More
@@ -0,0 +1,86
1 import tape = require("tape");
2 import { delay } from "./TestTraits";
3 import { Cancellation } from "@implab/core/Cancellation";
4 import { first, isPromise } from "@implab/core/safe";
5
6 tape("await delay test", async t => {
7 // schedule delay
8 let resolved = false;
9 let res = delay(0).then(() => resolved = true);
10
11 t.false(resolved, "the delay should be async");
12
13 await res;
14 t.pass("await delay");
15
16 // create cancellation token
17 let cancel: (e?: any) => void;
18 const ct = new Cancellation(c => cancel = c);
19
20 // schedule delay
21 resolved = false;
22 res = delay(0, ct).then(() => resolved = true);
23
24 t.false(resolved, "created delay with ct");
25
26 // cancel
27 cancel();
28
29 try {
30 await res;
31 t.fail("the delay should fail when it is cancelled");
32 } catch {
33 t.pass("the delay is cancelled");
34 }
35
36 let died = false;
37
38 // try schedule delay after the cancellation is requested
39 res = delay(0, ct).then(x => true, () => died = true);
40
41 t.false(died, "The delay should be scheduled even if the cancellation is requested");
42
43 await res;
44 t.true(died, "the delay should fail when cancelled");
45
46 t.end();
47 });
48
49 tape("sequemce test", async t => {
50 const sequence = ["a", "b", "c"];
51 const empty = [];
52
53 // synchronous tests
54 t.equals(first(sequence), "a", "Should return the first element");
55
56 let v: string;
57 let e: Error;
58 first(sequence, x => v = x);
59 t.equal(v, "a", "The callback should be called for the first element");
60
61 t.throws(() => {
62 first(empty);
63 }, "Should throw when the sequence is empty");
64
65 t.throws(() => {
66 first(empty, x => v = x);
67 }, "Should throw when the sequence is empty");
68
69 first(empty, null, x => e = x);
70 t.true(e, "The errorback should be called for the empty sequence");
71
72 // async tests
73 const asyncSequence = Promise.resolve(sequence);
74 const asyncEmptySequence = Promise.resolve(empty);
75
76 const promise = first(asyncSequence);
77 t.true(isPromise(promise), "Should return promise");
78
79 v = await promise;
80 t.equal(v, "a", "Should return the first element");
81
82 v = await new Promise(resolve => first(asyncSequence, resolve));
83 t.equal(v, "a", "The callback should be called for the first element");
84
85 t.end();
86 });
@@ -1,68 +1,68
1 1 # BUILD
2 2
3 3 Для сборки необходимо иметь
4 4
5 5 * nodejs >= 8
6 6 * npm
7 7 * eslint
8 8 * mercurial (для автоматического определения версии)
9 9
10 10 В пцессе сборки будут установлены зависимости из package.json
11 11
12 12 ## Properties
13 13
14 14 Свойства, испольуземые для управления сборкой, через них можно указать имя,
15 15 версию пакета, тип модулей, версию стандарта js для которого осуществляется
16 16 сборка. Значения по-умолчанию заданы в `gradle.properties`
17 17
18 18 ### npmName
19 19
20 20 `default: core`
21 21
22 22 Имя пакета в терминологии npm.
23 23
24 24 ### npmScope
25 25
26 26 `default: @implab`
27 27
28 28 Пространство в терминологии npm для пакета.
29 29
30 30 ### target
31 31
32 32 `default: es5`
33 33
34 34 Версии стандарта js в среде выполнения, возможные значения:
35 35
36 36 * es3 - требует полифилы для es5, promise
37 37 * es5 - требует полифил для promise
38 38 * es6
39 39 * es2015
40 40 * es2016
41 41 * es2017
42 42
43 43 ### jsmodule
44 44
45 45 `default: amd`
46 46
47 47 * amd - requirejs модули, хорошо подходит для использования в браузерах.
48 48 * commonjs - формат модулей для nodejs
49 49
50 50 ## Tasks
51 51
52 52 ### build
53 53
54 54 ### test
55 55
56 56 ### pack
57 57
58 58 ### publish
59 59
60 60 ## Examples
61 61
62 62 ```shell
63 63 ./gradlew test pack -PnpmName=core-amd
64 64 ```
65 65
66 66 ```shell
67 67 ./gradlew test pack -Pjsmodule=commonjs -Ptarget=es2017
68 ``` No newline at end of file
68 ```
@@ -1,88 +1,83
1 1 import { ICancellation, IDestroyable } from "./interfaces";
2 import { argumentNotNull } from "./safe";
3
4 const destroyed = {
5 destroy() {
6 }
7 };
2 import { argumentNotNull, destroyed } from "./safe";
8 3
9 4 export class Cancellation implements ICancellation {
10 5 private _reason: any;
11 private _cbs: Array<(e) => void>;
6 private _cbs: Array<(e: any) => void>;
12 7
13 constructor(action: (cancel: (e) => void) => void) {
8 constructor(action: (cancel: (e?: any) => void) => void) {
14 9 argumentNotNull(action, "action");
15 10
16 11 action(this._cancel.bind(this));
17 12 }
18 13
19 14 isSupported(): boolean {
20 15 return true;
21 16 }
22 17 throwIfRequested(): void {
23 18 if (this._reason)
24 19 throw this._reason;
25 20 }
26 21
27 22 isRequested(): boolean {
28 23 return !!this._reason;
29 24 }
30 25
31 26 register(cb: (e: any) => void): IDestroyable {
32 27 argumentNotNull(cb, "cb");
33 28
34 29 if (this._reason) {
35 30 cb(this._reason);
36 31 return destroyed;
37 32 } else {
38 33 if (!this._cbs)
39 34 this._cbs = [cb];
40 35 else
41 36 this._cbs.push(cb);
42 37
43 38 const me = this;
44 39 return {
45 40 destroy() {
46 41 me._unregister(cb);
47 42 }
48 43 };
49 44 }
50 45 }
51 46
52 47 private _unregister(cb) {
53 48 if (this._cbs) {
54 49 const i = this._cbs.indexOf(cb);
55 50 if (i >= 0)
56 51 this._cbs.splice(i, 1);
57 52 }
58 53 }
59 54
60 55 private _cancel(reason) {
61 56 if (this._reason)
62 57 return;
63 58
64 59 this._reason = (reason = reason || new Error("Operation cancelled"));
65 60
66 61 if (this._cbs) {
67 62 this._cbs.forEach(cb => cb(reason));
68 63 this._cbs = null;
69 64 }
70 65 }
71 66
72 67 static readonly none: ICancellation = {
73 68 isSupported(): boolean {
74 69 return false;
75 70 },
76 71
77 72 throwIfRequested(): void {
78 73 },
79 74
80 75 isRequested(): boolean {
81 76 return false;
82 77 },
83 78
84 79 register(_cb: (e: any) => void): IDestroyable {
85 80 return destroyed;
86 81 }
87 82 };
88 83 }
@@ -1,184 +1,208
1 import { IObservable, IDestroyable, ICancellation } from "./interfaces";
1 import { IObservable, IDestroyable, ICancellation, IObserver } from "./interfaces";
2 2 import { Cancellation } from "./Cancellation";
3 import { argumentNotNull } from "./safe";
3 import { argumentNotNull, destroyed } from "./safe";
4 4
5 5 type Handler<T> = (x: T) => void;
6 6
7 7 type Initializer<T> = (notify: Handler<T>, error?: (e: any) => void, complete?: () => void) => void;
8 8
9 // TODO: think about to move this interfaces.ts and make it public
10 interface IObserver<T> {
11 next(event: T): void;
9 const noop = () => { };
12 10
13 error(e: any): void;
14
15 complete(): void;
11 function isObserver(val: any): val is IObserver<any> {
12 return val && (typeof val.next === "function");
16 13 }
17 14
18 const noop = () => { };
19
20 15 export class Observable<T> implements IObservable<T> {
21 16 private _once = new Array<IObserver<T>>();
22 17
23 18 private _observers = new Array<IObserver<T>>();
24 19
25 20 private _complete: boolean;
26 21
27 22 private _error: any;
28 23
29 24 constructor(func?: Initializer<T>) {
30 25 if (func)
31 26 func(
32 27 this._notifyNext.bind(this),
33 28 this._notifyError.bind(this),
34 29 this._notifyCompleted.bind(this)
35 30 );
36 31 }
37 32
38 33 /**
39 34 * Registers handlers for the current observable object.
40 35 *
41 36 * @param next the handler for events
42 37 * @param error the handler for a error
43 38 * @param complete the handler for a completion
44 39 * @returns {IDestroyable} the handler for the current subscription, this
45 40 * handler can be used to unsubscribe from events.
46 41 *
47 42 */
48 43 on(next: Handler<T>, error?: Handler<any>, complete?: () => void): IDestroyable {
49 44 argumentNotNull(next, "next");
50 45
51 46 const me = this;
52 47
53 48 const observer: IObserver<T> & IDestroyable = {
54 49 next,
55 50 error: error ? error.bind(null) : noop,
56 51 complete: complete ? complete.bind(null) : noop,
57 52
58 53 destroy() {
59 54 me._removeObserver(this);
60 55 }
61 56 };
62 57
63 58 this._addObserver(observer);
64 59
65 60 return observer;
66 61 }
67 62
63 subscribe(next: IObserver<T> | Handler<T>, error?: Handler<any>, complete?: () => void): IDestroyable {
64 if (isObserver(next)) {
65 const me = this;
66 const subscription = {
67 destroy() {
68 me._removeObserver(next);
69 }
70 };
71 this._addObserver(next);
72 return subscription;
73 } else if (next) {
74 const observer = {
75 next,
76 error,
77 complete
78 };
79 const me = this;
80 const subscription = {
81 destroy() {
82 me._removeObserver(observer);
83 }
84 };
85 this._addObserver(observer);
86 return subscription;
87 } else {
88 return destroyed;
89 }
90 }
91
68 92 private _addObserver(observer: IObserver<T>) {
69 93 if (this._complete) {
70 94 try {
71 95 if (this._error)
72 96 observer.error(this._error);
73 97 else
74 98 observer.complete();
75 99 } catch (e) {
76 100 this.onObserverException(e);
77 101 }
78 102 } else {
79 103 this._observers.push(observer);
80 104 }
81 105 }
82 106
83 107 /**
84 108 * Waits for the next event. This method can't be used to read messages
85 109 * as a sequence since it can skip some messages between calls.
86 110 *
87 111 * @param ct a cancellation token
88 112 */
89 next(ct: ICancellation = Cancellation.none): Promise<T> {
113 next(ct: ICancellation = Cancellation.none) {
90 114 return new Promise<T>((resolve, reject) => {
91 115 const observer: IObserver<T> = {
92 116 next: resolve,
93 117 error: reject,
94 118 complete: () => reject("No more events are available")
95 119 };
96 120
97 121 if (this._addOnce(observer) && ct.isSupported()) {
98 122 ct.register(e => {
99 123 this._removeOnce(observer);
100 124 reject(e);
101 125 });
102 126 }
103 127 });
104 128 }
105 129
106 130 private _addOnce(observer: IObserver<T>) {
107 131 if (this._complete) {
108 132 try {
109 133 if (this._error)
110 134 observer.error(this._error);
111 135 else
112 136 observer.complete();
113 137 } catch (e) {
114 138 this.onObserverException(e);
115 139 }
116 140 return false;
117 141 }
118 142
119 143 this._once.push(observer);
120 144 return true;
121 145 }
122 146
123 147 protected onObserverException(e: any) {
124 148 }
125 149
126 150 private _removeOnce(d: IObserver<T>) {
127 151 const i = this._once.indexOf(d);
128 152 if (i >= 0)
129 153 this._once.splice(i, 1);
130 154 }
131 155
132 156 private _removeObserver(d: IObserver<T>) {
133 157 const i = this._observers.indexOf(d);
134 158 if (i >= 0)
135 159 this._observers.splice(i, 1);
136 160 }
137 161
138 162 private _notify(guard: (observer: IObserver<T>) => void) {
139 163 this._once.forEach(guard);
140 164 this._once = [];
141 165
142 166 this._observers.forEach(guard);
143 167 }
144 168
145 169 protected _notifyNext(evt: T) {
146 170 const guard = (observer: IObserver<T>) => {
147 171 try {
148 172 observer.next(evt);
149 173 } catch (e) {
150 174 this.onObserverException(e);
151 175 }
152 176 };
153 177
154 178 this._notify(guard);
155 179 }
156 180
157 181 protected _notifyError(e: any) {
158 182 const guard = (observer: IObserver<T>) => {
159 183 try {
160 184 observer.error(e);
161 185 } catch (e) {
162 186 this.onObserverException(e);
163 187 }
164 188 };
165 189
166 190 this._notify(guard);
167 191 this._observers = [];
168 192 this._complete = true;
169 193 }
170 194
171 195 protected _notifyCompleted() {
172 196 const guard = (observer: IObserver<T>) => {
173 197 try {
174 198 observer.complete();
175 199 } catch (e) {
176 200 this.onObserverException(e);
177 201 }
178 202 };
179 203
180 204 this._notify(guard);
181 205 this._observers = [];
182 206 this._complete = true;
183 207 }
184 208 }
@@ -1,348 +1,348
1 1 import {
2 2 ServiceRegistration,
3 3 TypeRegistration,
4 4 FactoryRegistration,
5 5 ServiceMap,
6 6 isDescriptor,
7 7 isDependencyRegistration,
8 8 DependencyRegistration,
9 9 ValueRegistration,
10 10 ActivationType,
11 11 isValueRegistration,
12 12 isTypeRegistration,
13 13 isFactoryRegistration
14 14 } from "./interfaces";
15 15
16 16 import { argumentNotEmptyString, isPrimitive, isPromise, delegate, argumentOfType, argumentNotNull, get } from "../safe";
17 17 import { AggregateDescriptor } from "./AggregateDescriptor";
18 18 import { ValueDescriptor } from "./ValueDescriptor";
19 19 import { Container } from "./Container";
20 20 import { ReferenceDescriptor } from "./ReferenceDescriptor";
21 21 import { TypeServiceDescriptor } from "./TypeServiceDescriptor";
22 22 import { FactoryServiceDescriptor } from "./FactoryServiceDescriptor";
23 23 import { TraceSource } from "../log/TraceSource";
24 24 import { ConfigError } from "./ConfigError";
25 25 import { Cancellation } from "../Cancellation";
26 26 import { makeResolver } from "./ResolverHelper";
27 27 import { ICancellation } from "../interfaces";
28 28
29 29 const trace = TraceSource.get("@implab/core/di/Configuration");
30 30
31 31 async function mapAll(data: object | any[], map?: (v, k) => any): Promise<any> {
32 32 if (data instanceof Array) {
33 33 return Promise.all(map ? data.map(map) : data);
34 34 } else {
35 35 const keys = Object.keys(data);
36 36
37 37 const o: any = {};
38 38
39 39 await Promise.all(keys.map(async k => {
40 40 const v = map ? map(data[k], k) : data[k];
41 41 o[k] = isPromise(v) ? await v : v;
42 42 }));
43 43
44 44 return o;
45 45 }
46 46 }
47 47
48 48 export type ModuleResolver = (moduleName: string, ct?: ICancellation) => any;
49 49
50 50 type _key = string | number;
51 51
52 52 export class Configuration {
53 53
54 54 _hasInnerDescriptors = false;
55 55
56 56 _container: Container;
57 57
58 58 _path: Array<_key>;
59 59
60 60 _configName: string;
61 61
62 62 _require: ModuleResolver;
63 63
64 64 constructor(container: Container) {
65 argumentNotNull(container, container);
65 argumentNotNull(container, "container");
66 66 this._container = container;
67 67 this._path = [];
68 68 }
69 69
70 70 async loadConfiguration(moduleName: string, contextRequire?: any, ct = Cancellation.none) {
71 71 argumentNotEmptyString(moduleName, "moduleName");
72 72
73 73 trace.log(
74 74 "loadConfiguration moduleName={0}, contextRequire={1}",
75 75 moduleName,
76 76 contextRequire ? typeof (contextRequire) : "<nil>"
77 77 );
78 78
79 79 this._configName = moduleName;
80 80
81 81 const r = await makeResolver(null, contextRequire);
82 82
83 83 const config = await r(moduleName, ct);
84 84
85 85 await this._applyConfiguration(
86 86 config,
87 87 await makeResolver(moduleName, contextRequire),
88 88 ct
89 89 );
90 90 }
91 91
92 92 async applyConfiguration(data: object, contextRequire?: any, ct = Cancellation.none) {
93 93 argumentNotNull(data, "data");
94 94
95 95 await this._applyConfiguration(data, await makeResolver(void (0), contextRequire), ct);
96 96 }
97 97
98 98 async _applyConfiguration(data: object, resolver?: ModuleResolver, ct = Cancellation.none) {
99 99 trace.log("applyConfiguration");
100 100
101 101 this._configName = "$";
102 102
103 103 if (resolver)
104 104 this._require = resolver;
105 105
106 106 let services: ServiceMap;
107 107
108 108 try {
109 109 services = await this._visitRegistrations(data, "$");
110 110 } catch (e) {
111 111 throw this._makeError(e);
112 112 }
113 113
114 114 this._container.register(services);
115 115 }
116 116
117 117 _makeError(inner) {
118 118 const e = new ConfigError("Failed to load configuration", inner);
119 119 e.configName = this._configName;
120 120 e.path = this._makePath();
121 121 return e;
122 122 }
123 123
124 124 _makePath() {
125 125 return this._path
126 126 .reduce(
127 127 (prev, cur) => typeof cur === "number" ?
128 128 `${prev}[${cur}]` :
129 129 `${prev}.${cur}`
130 130 )
131 131 .toString();
132 132 }
133 133
134 134 async _resolveType(moduleName: string, localName: string) {
135 135 trace.log("resolveType moduleName={0}, localName={1}", moduleName, localName);
136 136 try {
137 137 const m = await this._loadModule(moduleName);
138 138 return localName ? get(localName, m) : m;
139 139 } catch (e) {
140 140 trace.error("Failed to resolve type moduleName={0}, localName={1}", moduleName, localName);
141 141 throw e;
142 142 }
143 143 }
144 144
145 145 _loadModule(moduleName: string) {
146 146 trace.debug("loadModule {0}", moduleName);
147 147
148 148 return this._require(moduleName);
149 149 }
150 150
151 151 async _visitRegistrations(data, name: _key) {
152 152 this._enter(name);
153 153
154 154 if (data.constructor &&
155 155 data.constructor.prototype !== Object.prototype)
156 156 throw new Error("Configuration must be a simple object");
157 157
158 158 const o: ServiceMap = {};
159 159 const keys = Object.keys(data);
160 160
161 161 const services = await mapAll(data, async (v, k) => {
162 162 const d = await this._visit(v, k);
163 163 return isDescriptor(d) ? d : new AggregateDescriptor(d);
164 164 }) as ServiceMap;
165 165
166 166 this._leave();
167 167
168 168 return services;
169 169 }
170 170
171 171 _enter(name: _key) {
172 172 this._path.push(name);
173 173 trace.debug(">{0}", name);
174 174 }
175 175
176 176 _leave() {
177 177 const name = this._path.pop();
178 178 trace.debug("<{0}", name);
179 179 }
180 180
181 181 async _visit(data, name: string) {
182 182 if (isPrimitive(data) || isDescriptor(data))
183 183 return data;
184 184
185 185 if (isDependencyRegistration(data)) {
186 186 return this._visitDependencyRegistration(data, name);
187 187 } else if (isValueRegistration(data)) {
188 188 return this._visitValueRegistration(data, name);
189 189 } else if (isTypeRegistration(data)) {
190 190 return this._visitTypeRegistration(data, name);
191 191 } else if (isFactoryRegistration(data)) {
192 192 return this._visitFactoryRegistration(data, name);
193 193 } else if (data instanceof Array) {
194 194 return this._visitArray(data, name);
195 195 }
196 196
197 197 return this._visitObject(data, name);
198 198 }
199 199
200 200 async _visitObject(data: object, name: _key) {
201 201 if (data.constructor &&
202 202 data.constructor.prototype !== Object.prototype)
203 203 return new ValueDescriptor(data);
204 204
205 205 this._enter(name);
206 206
207 207 const v = await mapAll(data, delegate(this, "_visit"));
208 208
209 209 // TODO: handle inline descriptors properly
210 210 // const ex = {
211 211 // activate(ctx) {
212 212 // const value = ctx.activate(this.prop, "prop");
213 213 // // some code
214 214 // },
215 215 // // will be turned to ReferenceDescriptor
216 216 // prop: { $dependency: "depName" }
217 217 // };
218 218
219 219 this._leave();
220 220 return v;
221 221 }
222 222
223 223 async _visitArray(data: any[], name: _key) {
224 224 if (data.constructor &&
225 225 data.constructor.prototype !== Array.prototype)
226 226 return new ValueDescriptor(data);
227 227
228 228 this._enter(name);
229 229
230 230 const v = await mapAll(data, delegate(this, "_visit"));
231 231 this._leave();
232 232
233 233 return v;
234 234 }
235 235
236 236 _makeServiceParams(data: ServiceRegistration) {
237 237 const opts: any = {
238 238 owner: this._container
239 239 };
240 240 if (data.services)
241 241 opts.services = this._visitRegistrations(data.services, "services");
242 242
243 243 if (data.inject) {
244 244 this._enter("inject");
245 245 opts.inject = mapAll(
246 246 data.inject instanceof Array ?
247 247 data.inject :
248 248 [data.inject],
249 249 delegate(this, "_visitObject")
250 250 );
251 251 this._leave();
252 252 }
253 253
254 254 if ("params" in data)
255 255 opts.params = data.params instanceof Array ?
256 256 this._visitArray(data.params, "params") :
257 257 this._visit(data.params, "params");
258 258
259 259 if (data.activation) {
260 260 if (typeof (data.activation) === "string") {
261 261 switch (data.activation.toLowerCase()) {
262 262 case "singleton":
263 263 opts.activation = ActivationType.Singleton;
264 264 break;
265 265 case "container":
266 266 opts.activation = ActivationType.Container;
267 267 break;
268 268 case "hierarchy":
269 269 opts.activation = ActivationType.Hierarchy;
270 270 break;
271 271 case "context":
272 272 opts.activation = ActivationType.Context;
273 273 break;
274 274 case "call":
275 275 opts.activation = ActivationType.Call;
276 276 break;
277 277 default:
278 278 throw new Error("Unknown activation type: " +
279 279 data.activation);
280 280 }
281 281 } else {
282 282 opts.activation = Number(data.activation);
283 283 }
284 284 }
285 285
286 286 if (data.cleanup)
287 287 opts.cleanup = data.cleanup;
288 288
289 289 return opts;
290 290 }
291 291
292 292 async _visitValueRegistration(data: ValueRegistration, name: _key) {
293 293 this._enter(name);
294 294 const d = data.parse ? new AggregateDescriptor(data.$value) : new ValueDescriptor(data.$value);
295 295 this._leave();
296 296 return d;
297 297 }
298 298
299 299 async _visitDependencyRegistration(data: DependencyRegistration, name: _key) {
300 300 argumentNotEmptyString(data && data.$dependency, "data.$dependency");
301 301 this._enter(name);
302 302 const d = new ReferenceDescriptor({
303 303 name: data.$dependency,
304 304 lazy: data.lazy,
305 305 optional: data.optional,
306 306 default: data.default,
307 307 services: data.services && await this._visitRegistrations(data.services, "services")
308 308 });
309 309 this._leave();
310 310 return d;
311 311 }
312 312
313 313 async _visitTypeRegistration(data: TypeRegistration, name: _key) {
314 314 argumentNotNull(data.$type, "data.$type");
315 315 this._enter(name);
316 316
317 317 const opts = this._makeServiceParams(data);
318 318 if (data.$type instanceof Function) {
319 319 opts.type = data.$type;
320 320 } else {
321 321 const [moduleName, typeName] = data.$type.split(":", 2);
322 322 opts.type = this._resolveType(moduleName, typeName);
323 323 }
324 324
325 325 const d = new TypeServiceDescriptor(
326 326 await mapAll(opts)
327 327 );
328 328
329 329 this._leave();
330 330
331 331 return d;
332 332 }
333 333
334 334 async _visitFactoryRegistration(data: FactoryRegistration, name: _key) {
335 335 argumentOfType(data.$factory, Function, "data.$factory");
336 336 this._enter(name);
337 337
338 338 const opts = this._makeServiceParams(data);
339 339 opts.factory = data.$factory;
340 340
341 341 const d = new FactoryServiceDescriptor(
342 342 await mapAll(opts)
343 343 );
344 344
345 345 this._leave();
346 346 return d;
347 347 }
348 348 }
@@ -1,93 +1,101
1 1 export interface Constructor<T = {}> {
2 2 new (...args: any[]): T;
3 3 prototype: T;
4 4 }
5 5
6 6 export type Factory<T = {}> = (...args: any[]) => T;
7 7
8 8 export type Predicate<T = any> = (x: T) => boolean;
9 9
10 10 export interface MapOf<T> {
11 11 [key: string]: T;
12 12 }
13 13
14 14 export interface IDestroyable {
15 15 destroy(): void;
16 16 }
17 17
18 18 export interface IRemovable {
19 19 remove(): void;
20 20 }
21 21
22 22 export interface ICancellation {
23 23 throwIfRequested(): void;
24 24 isRequested(): boolean;
25 25 isSupported(): boolean;
26 26 register(cb: (e: any) => void): IDestroyable;
27 27 }
28 28
29 29 /**
30 30 * Интерфейс поддерживающий асинхронную активацию
31 31 */
32 32 export interface IActivatable {
33 33 /**
34 34 * @returns Boolean indicates the current state
35 35 */
36 36 isActive(): boolean;
37 37
38 38 /**
39 39 * Starts the component activation
40 40 * @param ct cancellation token for this operation
41 41 */
42 42 activate(ct?: ICancellation): Promise<void>;
43 43
44 44 /**
45 45 * Starts the component deactivation
46 46 * @param ct cancellation token for this operation
47 47 */
48 48 deactivate(ct?: ICancellation): Promise<void>;
49 49
50 50 /**
51 51 * Sets the activation controller for this component
52 52 * @param controller The activation controller
53 53 *
54 54 * Activation controller checks whether this component
55 55 * can be activated and manages the active state of the
56 56 * component
57 57 */
58 58 setActivationController(controller: IActivationController);
59 59
60 60 /**
61 61 * Gets the current activation controller for this component
62 62 */
63 63 getActivationController(): IActivationController;
64 64 }
65 65
66 66 export interface IActivationController {
67 67 activating(component: IActivatable, ct?: ICancellation): Promise<void>;
68 68
69 69 activated(component: IActivatable, ct?: ICancellation): Promise<void>;
70 70
71 71 deactivating(component: IActivatable, ct?: ICancellation): Promise<void>;
72 72
73 73 deactivated(component: IActivatable, ct?: ICancellation): Promise<void>;
74 74
75 75 deactivate(ct?: ICancellation): Promise<void>;
76 76
77 77 activate(component: IActivatable, ct?: ICancellation): Promise<void>;
78 78
79 79 getActive(): IActivatable;
80 80 }
81 81
82 82 export interface IAsyncComponent {
83 83 getCompletion(): Promise<void>;
84 84 }
85 85
86 86 export interface ICancellable {
87 87 cancel(reason?: any): void;
88 88 }
89 89
90 90 export interface IObservable<T> {
91 91 on(next: (x: T) => void, error?: (e: any) => void, complete?: () => void): IDestroyable;
92 92 next(ct?: ICancellation): Promise<T>;
93 93 }
94
95 export interface IObserver<T> {
96 next(event: T): void;
97
98 error(e: any): void;
99
100 complete(): void;
101 }
@@ -1,428 +1,456
1 1 import { ICancellable, Constructor } from "./interfaces";
2 import { Cancellation } from "./Cancellation";
2 3
3 4 let _nextOid = 0;
4 5 const _oid = typeof Symbol === "function" ?
5 6 Symbol("__implab__oid__") :
6 7 "__implab__oid__";
7 8
8 9 export function oid(instance: object): string {
9 10 if (isNull(instance))
10 11 return null;
11 12
12 13 if (_oid in instance)
13 14 return instance[_oid];
14 15 else
15 16 return (instance[_oid] = "oid_" + (++_nextOid));
16 17 }
17 18
18 19 export function argumentNotNull(arg: any, name: string) {
19 20 if (arg === null || arg === undefined)
20 21 throw new Error("The argument " + name + " can't be null or undefined");
21 22 }
22 23
23 24 export function argumentNotEmptyString(arg: any, name: string) {
24 25 if (typeof (arg) !== "string" || !arg.length)
25 26 throw new Error("The argument '" + name + "' must be a not empty string");
26 27 }
27 28
28 29 export function argumentNotEmptyArray(arg: any, name: string) {
29 30 if (!(arg instanceof Array) || !arg.length)
30 31 throw new Error("The argument '" + name + "' must be a not empty array");
31 32 }
32 33
33 34 export function argumentOfType(arg: any, type: Constructor<{}>, name: string) {
34 35 if (!(arg instanceof type))
35 36 throw new Error("The argument '" + name + "' type doesn't match");
36 37 }
37 38
38 39 export function isNull(val: any) {
39 40 return (val === null || val === undefined);
40 41 }
41 42
42 43 export function isPrimitive(val: any): val is string | number | boolean | undefined | null {
43 44 return (val === null || val === undefined || typeof (val) === "string" ||
44 45 typeof (val) === "number" || typeof (val) === "boolean");
45 46 }
46 47
47 48 export function isInteger(val: any): val is number {
48 49 return parseInt(val, 10) === val;
49 50 }
50 51
51 52 export function isNumber(val: any): val is number {
52 53 return parseFloat(val) === val;
53 54 }
54 55
55 56 export function isString(val: any): val is string {
56 57 return typeof (val) === "string" || val instanceof String;
57 58 }
58 59
59 60 export function isPromise(val: any): val is PromiseLike<any> {
60 61 return val && typeof val.then === "function";
61 62 }
62 63
63 64 export function isCancellable(val: any): val is ICancellable {
64 65 return val && typeof val.cancel === "function";
65 66 }
66 67
67 68 export function isNullOrEmptyString(val: any): val is string | null | undefined {
68 69 if (val === null || val === undefined ||
69 70 ((typeof (val) === "string" || val instanceof String) && val.length === 0))
70 71 return true;
71 72 }
72 73
73 74 export function isNotEmptyArray(arg: any): arg is Array<any> {
74 75 return (arg instanceof Array && arg.length > 0);
75 76 }
76 77
77 78 function _isStrictMode() {
78 79 return !this;
79 80 }
80 81
81 82 function _getNonStrictGlobal() {
82 83 return this;
83 84 }
84 85
85 86 export function getGlobal() {
86 87 // in es3 we can't use indirect call to eval, since it will
87 88 // be executed in the current call context.
88 89 if (!_isStrictMode()) {
89 90 return _getNonStrictGlobal();
90 91 } else {
91 92 // tslint:disable-next-line:no-eval
92 93 return eval.call(null, "this");
93 94 }
94 95 }
95 96
96 97 export function get(member: string, context?: object) {
97 98 argumentNotEmptyString(member, "member");
98 99 let that = context || getGlobal();
99 100 const parts = member.split(".");
100 101 for (const m of parts) {
101 102 if (!m)
102 103 continue;
103 104 if (isNull(that = that[m]))
104 105 break;
105 106 }
106 107 return that;
107 108 }
108 109
109 110 /**
110 111 * Выполняет метод для каждого элемента массива, останавливается, когда
111 112 * либо достигнут конец массива, либо функция <c>cb</c> вернула
112 113 * значение.
113 114 *
114 115 * @param {Array | Object} obj массив элементов для просмотра
115 116 * @param {Function} cb функция, вызываемая для каждого элемента
116 117 * @param {Object} thisArg значение, которое будет передано в качестве
117 118 * <c>this</c> в <c>cb</c>.
118 119 * @returns Результат вызова функции <c>cb</c>, либо <c>undefined</c>
119 120 * если достигнут конец массива.
120 121 */
121 122 export function each(obj, cb, thisArg?) {
122 123 argumentNotNull(cb, "cb");
123 124 if (obj instanceof Array) {
124 125 for (let i = 0; i < obj.length; i++) {
125 126 const x = cb.call(thisArg, obj[i], i);
126 127 if (x !== undefined)
127 128 return x;
128 129 }
129 130 } else {
130 131 const keys = Object.keys(obj);
131 132 for (const k of keys) {
132 133 const x = cb.call(thisArg, obj[k], k);
133 134 if (x !== undefined)
134 135 return x;
135 136 }
136 137 }
137 138 }
138 139
139 140 /** Copies property values from a source object to the destination and returns
140 141 * the destination onject.
141 142 *
142 143 * @param dest The destination object into which properties from the source
143 144 * object will be copied.
144 145 * @param source The source of values which will be copied to the destination
145 146 * object.
146 147 * @param template An optional parameter specifies which properties should be
147 148 * copied from the source and how to map them to the destination. If the
148 149 * template is an array it contains the list of property names to copy from the
149 150 * source to the destination. In case of object the templates contains the map
150 151 * where keys are property names in the source and the values are property
151 152 * names in the destination object. If the template isn't specified then the
152 153 * own properties of the source are entirely copied to the destination.
153 154 *
154 155 */
155 156 export function mixin<T, S>(dest: T, source: S, template?: string[] | object): T & S {
156 157 argumentNotNull(dest, "to");
157 158 const _res = dest as T & S;
158 159
159 160 if (isPrimitive(source))
160 161 return _res;
161 162
162 163 if (template instanceof Array) {
163 164 for (const p of template) {
164 165 if (p in source)
165 166 _res[p] = source[p];
166 167 }
167 168 } else if (template) {
168 169 const keys = Object.keys(source);
169 170 for (const p of keys) {
170 171 if (p in template)
171 172 _res[template[p]] = source[p];
172 173 }
173 174 } else {
174 175 const keys = Object.keys(source);
175 176 for (const p of keys)
176 177 _res[p] = source[p];
177 178 }
178 179
179 180 return _res;
180 181 }
181 182
182 183 /** Wraps the specified function to emulate an asynchronous execution.
183 184 * @param{Object} thisArg [Optional] Object which will be passed as 'this' to the function.
184 185 * @param{Function|String} fn [Required] Function wich will be wrapped.
185 186 */
186 187 export function async(_fn: (...args: any[]) => any, thisArg): (...args: any[]) => PromiseLike<any> {
187 188 let fn = _fn;
188 189
189 190 if (arguments.length === 2 && !(fn instanceof Function))
190 191 fn = thisArg[fn];
191 192
192 193 if (fn == null)
193 194 throw new Error("The function must be specified");
194 195
195 196 function wrapresult(x, e?): PromiseLike<any> {
196 197 if (e) {
197 198 return {
198 199 then(cb, eb) {
199 200 try {
200 201 return eb ? wrapresult(eb(e)) : this;
201 202 } catch (e2) {
202 203 return wrapresult(null, e2);
203 204 }
204 205 }
205 206 };
206 207 } else {
207 208 if (x && x.then)
208 209 return x;
209 210 return {
210 211 then(cb) {
211 212 try {
212 213 return cb ? wrapresult(cb(x)) : this;
213 214 } catch (e2) {
214 215 return wrapresult(e2);
215 216 }
216 217 }
217 218 };
218 219 }
219 220 }
220 221
221 222 return (...args) => {
222 223 try {
223 224 return wrapresult(fn.apply(thisArg, args));
224 225 } catch (e) {
225 226 return wrapresult(null, e);
226 227 }
227 228 };
228 229 }
229 230
230 231 type _AnyFn = (...args) => any;
231 232
232 233 export function delegate<T, K extends keyof T>(target: T, _method: (K | _AnyFn)) {
233 234 let method;
234 235
235 236 if (!(_method instanceof Function)) {
236 237 argumentNotNull(target, "target");
237 238 method = target[_method];
238 239 if (!(method instanceof Function))
239 240 throw new Error("'method' argument must be a Function or a method name");
240 241 } else {
241 242 method = _method;
242 243 }
243 244
244 245 return (...args) => {
245 246 return method.apply(target, args);
246 247 };
247 248 }
248 249
250 export function delay(timeMs: number, ct = Cancellation.none) {
251 return new Promise((resolve, reject) => {
252 if (ct.isRequested()) {
253 ct.register(reject);
254 } else {
255 const h = ct.register(e => {
256 clearTimeout(id);
257 reject(e);
258 // we don't nedd to unregister h, since ct is already disposed
259 });
260 const id = setTimeout(() => {
261 h.destroy();
262 resolve();
263 }, timeMs);
264 }
265 });
266 }
267
249 268 /**
250 269 * Для каждого элемента массива вызывает указанную функцию и сохраняет
251 270 * возвращенное значение в массиве результатов.
252 271 *
253 272 * @remarks cb может выполняться асинхронно, при этом одновременно будет
254 273 * только одна операция.
255 274 *
256 275 * @async
257 276 */
258 277 export function pmap(items, cb) {
259 278 argumentNotNull(cb, "cb");
260 279
261 280 if (isPromise(items))
262 281 return items.then(data => pmap(data, cb));
263 282
264 283 if (isNull(items) || !items.length)
265 284 return items;
266 285
267 286 let i = 0;
268 287 const result = [];
269 288
270 289 function next() {
271 290 let r;
272 291 let ri;
273 292
274 293 function chain(x) {
275 294 result[ri] = x;
276 295 return next();
277 296 }
278 297
279 298 while (i < items.length) {
280 299 r = cb(items[i], i);
281 300 ri = i;
282 301 i++;
283 302 if (isPromise(r)) {
284 303 return r.then(chain);
285 304 } else {
286 305 result[ri] = r;
287 306 }
288 307 }
289 308 return result;
290 309 }
291 310
292 311 return next();
293 312 }
294 313
295 314 export function pfor(items, cb) {
296 315 argumentNotNull(cb, "cb");
297 316
298 317 if (isPromise(items))
299 318 return items.then(data => {
300 319 return pmap(data, cb);
301 320 });
302 321
303 322 if (isNull(items) || !items.length)
304 323 return items;
305 324
306 325 let i = 0;
307 326
308 327 function next() {
309 328 while (i < items.length) {
310 329 const r = cb(items[i], i);
311 330 i++;
312 331 if (isPromise(r))
313 332 return r.then(next);
314 333 }
315 334 }
316 335
317 336 return next();
318 337 }
319 338
320 339 export function first<T>(sequence: ArrayLike<T>): T;
321 340 export function first<T>(sequence: PromiseLike<ArrayLike<T>>): PromiseLike<T>;
322 341 export function first<T>(
323 342 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
324 343 cb: (x: T) => void,
325 344 err?: (x: Error) => void
326 345 ): void;
327 346 /**
328 347 * Выбирает первый элемент из последовательности, или обещания, если в
329 348 * качестве параметра используется обещание, оно должно вернуть массив.
330 349 *
331 350 * @param {Function} cb обработчик результата, ему будет передан первый
332 351 * элемент последовательности в случае успеха
333 352 * @param {Function} err обработчик исключения, если массив пустой, либо
334 353 * не массив
335 354 *
336 355 * @remarks Если не указаны ни cb ни err, тогда функция вернет либо
337 356 * обещание, либо первый элемент.
338 357 * @async
339 358 */
340 359 export function first<T>(
341 360 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
342 361 cb?: (x: T) => void,
343 362 err?: (x: Error) => void
344 363 ) {
345 364 if (isPromise(sequence)) {
346 365 return sequence.then(res => first(res, cb, err));
347 366 } else if (sequence && "length" in sequence) {
348 367 if (sequence.length === 0) {
349 368 if (err)
350 369 return err(new Error("The sequence is empty"));
351 370 else
352 371 throw new Error("The sequence is empty");
353 372 } else if (cb) {
354 373 cb(sequence[0]);
355 374 } else {
356 375 return sequence[0];
357 376 }
358 377 } else {
359 378 if (err)
360 379 err(new Error("The sequence is required"));
361 380 else
362 381 throw new Error("The sequence is required");
363 382 }
364 383 }
365 384
366 385 export function firstWhere<T>(
367 386 sequence: ArrayLike<T>,
368 387 predicate: (x: T) => boolean
369 388 ): T;
370 389 export function firstWhere<T>(
371 390 sequence: PromiseLike<ArrayLike<T>>,
372 391 predicate: (x: T) => boolean
373 392 ): PromiseLike<T>;
374 393 export function firstWhere<T>(
375 394 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
376 395 predicate: (x: T) => boolean,
377 396 cb: (x: T) => void,
378 397 err?: (x: Error) => void
379 398 ): void;
380 399
381 400 export function firstWhere<T>(
382 401 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
383 402 predicate?: (x: T) => boolean,
384 cb?: (x: T) => void,
385 err?: (x: Error) => void
403 cb?: (x: T) => any,
404 err?: (x: Error) => any
386 405 ) {
387 406 if (isPromise(sequence)) {
388 407 return sequence.then(res => firstWhere(res, predicate, cb, err));
389 408 } else if (sequence && "length" in sequence) {
390 409 if (sequence.length === 0) {
391 410 if (err)
392 411 err(new Error("The sequence is empty"));
393 412 else
394 413 throw new Error("The sequence is empty");
395 414 } else {
396 415 if (!predicate) {
397 return cb ? cb(sequence[0]) : sequence[0];
416 return cb ? cb(sequence[0]) && void (0) : sequence[0];
398 417 } else {
399 418 for (let i = 0; i < sequence.length; i++) {
400 419 const v = sequence[i];
401 420 if (predicate(v))
402 421 return cb ? cb(v) : v;
403 422 }
404 423 if (err)
405 424 err(new Error("The sequence doesn't contain matching items"));
406 425 else
407 426 throw new Error("The sequence doesn't contain matching items");
408 427 }
409 428 }
410 429 } else {
411 430 if (err)
412 431 err(new Error("The sequence is required"));
413 432 else
414 433 throw new Error("The sequence is required");
415 434 }
416 435 }
417 436
418 437 export function destroy(d: any) {
419 438 if (d && "destroy" in d)
420 439 d.destroy();
421 440 }
422 441
423 442 /**
424 443 * Used to mark that the async operation isn't awaited intentionally.
425 444 * @param p The promise which represents the async operation.
426 445 */
427 446 export function nowait(p: Promise<any>) {
428 447 }
448
449 /** represents already destroyed object.
450 */
451 export const destroyed = {
452 /** Calling to this method doesn't affect anything, noop.
453 */
454 destroy() {
455 }
456 };
@@ -1,8 +1,9
1 1 define([
2 2 "./ActivatableTests",
3 3 "./trace-test",
4 4 "./TraceSourceTests",
5 5 "./CancellationTests",
6 6 "./ObservableTests",
7 "./ContainerTests"
7 "./ContainerTests",
8 "./SafeTests"
8 9 ]); No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now