##// END OF EJS Templates
minor fixed, improved typings
cin -
r154:8e623248bc9d v1.4.0-rc12 default
parent child
Show More
@@ -1,174 +1,177
1 1 import { ActivationContext } from "./ActivationContext";
2 2 import { ValueDescriptor } from "./ValueDescriptor";
3 3 import { ActivationError } from "./ActivationError";
4 4 import { ServiceMap, Descriptor, PartialServiceMap, ContainerServiceMap, ContainerKeys, TypeOfService, ServiceContainer } from "./interfaces";
5 5 import { TraceSource } from "../log/TraceSource";
6 6 import { Configuration, RegistrationMap } from "./Configuration";
7 7 import { Cancellation } from "../Cancellation";
8 8 import { ICancellation } from "../interfaces";
9 9 import { isDescriptor } from "./traits";
10 10 import { LifetimeManager } from "./LifetimeManager";
11 11 import { each, isString } from "../safe";
12 12 import { ContainerConfiguration, FluentRegistrations } from "./fluent/interfaces";
13 13 import { FluentConfiguration } from "./fluent/FluentConfiguration";
14 14
15 15 const trace = TraceSource.get("@implab/core/di/ActivationContext");
16 16
17 17 export class Container<S extends object = any> implements ServiceContainer<S> {
18 18 readonly _services: ContainerServiceMap<S>;
19 19
20 20 readonly _lifetimeManager: LifetimeManager;
21 21
22 22 readonly _cleanup: (() => void)[];
23 23
24 24 readonly _root: Container<S>;
25 25
26 26 readonly _parent?: Container<S>;
27 27
28 28 _disposed: boolean;
29 29
30 30 constructor(parent?: Container<S>) {
31 31 this._parent = parent;
32 32 this._services = Object.create(parent ? parent._services : null);
33 33 this._cleanup = [];
34 34 this._root = parent ? parent.getRootContainer() : this;
35 35 this._services.container = new ValueDescriptor(this) as any;
36 36 this._services.childContainer = { activate: () => this.createChildContainer() };
37 37 this._disposed = false;
38 38 this._lifetimeManager = new LifetimeManager();
39 39 }
40 40
41 41 getRootContainer() {
42 42 return this._root;
43 43 }
44 44
45 45 getParent() {
46 46 return this._parent;
47 47 }
48 48
49 49 getLifetimeManager() {
50 50 return this._lifetimeManager;
51 51 }
52 52
53 resolve<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>): TypeOfService<S, K> {
53 resolve<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>): TypeOfService<S, K>;
54 resolve<K extends ContainerKeys<S>>(name: K, def: undefined): TypeOfService<S, K> | undefined;
55 resolve<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>): TypeOfService<S, K> | undefined {
54 56 trace.debug("resolve {0}", name);
55 57 const d = this._services[name];
56 58 if (d === undefined) {
57 if (def !== undefined)
59 if (arguments.length > 1)
58 60 return def;
59 61 else
60 62 throw new Error("Service '" + name + "' isn't found");
61 63 } else {
62 64
63 65 const context = new ActivationContext<S>(this, this._services, String(name), d);
64 66 try {
65 67 return d.activate(context);
66 68 } catch (error) {
67 69 throw new ActivationError(name.toString(), context.getStack(), error);
68 70 }
69 71 }
70 72 }
71 73
72 74 /**
73 75 * @deprecated use resolve() method
74 76 */
75 77 getService<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>) {
76 return this.resolve(name, def);
78 return arguments.length === 1 ? this.resolve(name) : this.resolve(name, def);
77 79 }
78 80
79 81 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>): this;
80 82 register(services: PartialServiceMap<S>): this;
81 83 register<K extends keyof S>(nameOrCollection: K | ServiceMap<S>, service?: Descriptor<S, S[K]>) {
82 84 if (arguments.length === 1) {
83 85 const data = nameOrCollection as ServiceMap<S>;
84 86
85 87 each(data, (v, k) => this.register(k, v));
86 88 } else {
87 89 if (!isDescriptor(service))
88 90 throw new Error("The service parameter must be a descriptor");
89 91
90 92 this._services[nameOrCollection as K] = service as any;
91 93 }
92 94 return this;
93 95 }
94 96
95 97 /** @deprecated use getLifetimeManager() */
96 98 onDispose(callback: () => void) {
97 99 if (!(callback instanceof Function))
98 100 throw new Error("The callback must be a function");
99 101 this._cleanup.push(callback);
100 102 }
101 103
102 104 destroy() {
103 105 return this.dispose();
104 106 }
105 107 dispose() {
106 108 if (this._disposed)
107 109 return;
108 110 this._disposed = true;
109 111 for (const f of this._cleanup)
110 112 f();
113 this._lifetimeManager.destroy();
111 114 }
112 115
113 116 /**
114 117 * @param{String|Object} config
115 118 * The configuration of the container. Can be either a string or an object,
116 119 * if the configuration is an object it's treated as a collection of
117 120 * services which will be registered in the container.
118 121 *
119 122 * @param{Function} opts.contextRequire
120 123 * The function which will be used to load a configuration or types for services.
121 124 *
122 125 */
123 126 async configure(config: string | RegistrationMap<S>, opts?: { contextRequire: any; baseModule?: string }, ct = Cancellation.none) {
124 127 const _opts = Object.create(opts || null);
125 128
126 129 if (typeof (config) === "string") {
127 130 _opts.baseModule = config;
128 131
129 132 const module = await import(config);
130 133 if (module && module.default && typeof (module.default.apply) === "function")
131 134 return module.default.apply(this);
132 135 else
133 136 return this._applyLegacyConfig(module, _opts, ct);
134 137 } else {
135 138 return this._applyLegacyConfig(config, _opts, ct);
136 139 }
137 140 }
138 141
139 142 applyConfig<S2 extends object>(config: Promise<{ default: ContainerConfiguration<S2>; }>, ct?: ICancellation): Promise<ServiceContainer<S & S2>>;
140 143 applyConfig<S2 extends object, P extends string>(config: Promise<{ [p in P]: ContainerConfiguration<S2>; }>, prop: P, ct?: ICancellation): Promise<ServiceContainer<S & S2>>;
141 144 async applyConfig<S2 extends object, P extends string>(
142 145 config: Promise<{ [p in P | "default"]: ContainerConfiguration<S2>; }>,
143 146 propOrCt?: P | ICancellation,
144 147 ct?: ICancellation
145 148 ): Promise<ServiceContainer<S & S2>> {
146 149 const mod = await config;
147 150
148 151 let _ct: ICancellation;
149 152 let _prop: P | "default";
150 153
151 154 if (isString(propOrCt)) {
152 155 _prop = propOrCt;
153 156 _ct = ct || Cancellation.none;
154 157 } else {
155 158 _ct = propOrCt || Cancellation.none;
156 159 _prop = "default";
157 160 }
158 161
159 162 return mod[_prop].apply(this, _ct);
160 163 }
161 164
162 165 async _applyLegacyConfig(config: RegistrationMap<S>, opts: { contextRequire: any; baseModule?: string }, ct = Cancellation.none) {
163 166 return new Configuration<S>(this).applyConfiguration(config, opts);
164 167 }
165 168
166 169 async fluent<K extends keyof S>(config: FluentRegistrations<K, S>, ct = Cancellation.none): Promise<this> {
167 170 await new FluentConfiguration<S>().register(config).apply(this, ct);
168 171 return this;
169 172 }
170 173
171 174 createChildContainer<S2 extends object = S>(): Container<S & S2> {
172 175 return new Container<S & S2>(this as any);
173 176 }
174 177 }
@@ -1,197 +1,199
1 1 import { IDestroyable, MapOf } from "../interfaces";
2 import { argumentNotNull, isDestroyable, argumentNotEmptyString } from "../safe";
2 import { argumentNotNull, isDestroyable, argumentNotEmptyString, isRemovable } from "../safe";
3 3 import { ILifetime, ServiceContainer } from "./interfaces";
4 4 import { ActivationContext } from "./ActivationContext";
5 5
6 6 function safeCall(item: () => void) {
7 7 try {
8 8 item();
9 9 } catch {
10 10 // silence!
11 11 }
12 12 }
13 13
14 14 const emptyLifetime: ILifetime = Object.freeze({
15 15 has() {
16 16 return false;
17 17 },
18 18
19 19 initialize() {
20 20
21 21 },
22 22
23 23 get() {
24 24 throw new Error("The specified item isn't registered with this lifetime manager");
25 25 },
26 26
27 27 store() {
28 28 // does nothing
29 29 }
30 30
31 31 });
32 32
33 33 const unknownLifetime: ILifetime = Object.freeze({
34 34 has() {
35 35 return false;
36 36 },
37 37 initialize() {
38 38 throw new Error("Can't call initialize on the unknown lifetime object");
39 39 },
40 40 get() {
41 41 throw new Error("The lifetime object isn't initialized");
42 42 },
43 43 store() {
44 44 throw new Error("Can't store a value in the unknown lifetime object");
45 45 }
46 46 });
47 47
48 48 let nextId = 0;
49 49
50 50 const singletons: any = {};
51 51
52 52 export class LifetimeManager implements IDestroyable {
53 53 private _cleanup: (() => void)[] = [];
54 54 private _cache: MapOf<any> = {};
55 55 private _destroyed = false;
56 56
57 57 private _pending: MapOf<boolean> = {};
58 58
59 59 create(): ILifetime {
60 60 const self = this;
61 61 const id = ++nextId;
62 62 return {
63 63 has() {
64 64 return (id in self._cache);
65 65 },
66 66
67 67 get() {
68 68 const t = self._cache[id];
69 69 if (t === undefined)
70 70 throw new Error(`The item with with the key ${id} isn't found`);
71 71 return t;
72 72 },
73 73
74 74 initialize() {
75 75 if (self._pending[id])
76 76 throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`);
77 77 self._pending[id] = true;
78 78 },
79 79
80 80 store(item: any, cleanup?: (item: any) => void) {
81 81 argumentNotNull(id, "id");
82 82 argumentNotNull(item, "item");
83 83
84 84 if (this.has())
85 85 throw new Error(`The item with with the key ${id} already registered with this lifetime manager`);
86 86 delete self._pending[id];
87 87
88 88 self._cache[id] = item;
89 89
90 90 if (self._destroyed)
91 91 throw new Error("Lifetime manager is destroyed");
92 92 if (cleanup) {
93 93 self._cleanup.push(() => cleanup(item));
94 94 } else if (isDestroyable(item)) {
95 95 self._cleanup.push(() => item.destroy());
96 } else if (isRemovable(item)) {
97 self._cleanup.push(() => item.remove());
96 98 }
97 99 }
98 100 };
99 101 }
100 102
101 103 destroy() {
102 104 if (!this._destroyed) {
103 105 this._destroyed = true;
104 106 this._cleanup.forEach(safeCall);
105 107 this._cleanup.length = 0;
106 108 }
107 109 }
108 110
109 111 static empty(): ILifetime {
110 112 return emptyLifetime;
111 113 }
112 114
113 115 static hierarchyLifetime(): ILifetime {
114 116 let _lifetime = unknownLifetime;
115 117 return {
116 118 initialize(context: ActivationContext<any>) {
117 119 if (_lifetime !== unknownLifetime)
118 120 throw new Error("Cyclic reference activation detected");
119 121
120 122 _lifetime = context.getContainer().getLifetimeManager().create();
121 123 },
122 124 get() {
123 125 return _lifetime.get();
124 126 },
125 127 has() {
126 128 return _lifetime.has();
127 129 },
128 130 store(item: any, cleanup?: (item: any) => void) {
129 131 return _lifetime.store(item, cleanup);
130 132 }
131 133 };
132 134 }
133 135
134 136 static contextLifetime(): ILifetime {
135 137 let _lifetime = unknownLifetime;
136 138 return {
137 139 initialize(context: ActivationContext<any>) {
138 140 if (_lifetime !== unknownLifetime)
139 141 throw new Error("Cyclic reference detected");
140 142 _lifetime = context.createLifetime();
141 143 },
142 144 get() {
143 145 return _lifetime.get();
144 146 },
145 147 has() {
146 148 return _lifetime.has();
147 149 },
148 150 store(item: any) {
149 151 _lifetime.store(item);
150 152 }
151 153 };
152 154 }
153 155
154 156 static singletonLifetime(typeId: string): ILifetime {
155 157 argumentNotEmptyString(typeId, "typeId");
156 158 let pending = false;
157 159 return {
158 160 has() {
159 161 return typeId in singletons;
160 162 },
161 163 get() {
162 164 if (!this.has())
163 165 throw new Error(`The instance ${typeId} doesn't exists`);
164 166 return singletons[typeId];
165 167 },
166 168 initialize() {
167 169 if (pending)
168 170 throw new Error("Cyclic reference detected");
169 171 pending = true;
170 172 },
171 173 store(item: any) {
172 174 singletons[typeId] = item;
173 175 pending = false;
174 176 }
175 177 };
176 178 }
177 179
178 180 static containerLifetime(container: ServiceContainer<any>) {
179 181 let _lifetime = unknownLifetime;
180 182 return {
181 183 initialize(context: ActivationContext<any>) {
182 184 if (_lifetime !== unknownLifetime)
183 185 throw new Error("Cyclic reference detected");
184 186 _lifetime = container.getLifetimeManager().create();
185 187 },
186 188 get() {
187 189 return _lifetime.get();
188 190 },
189 191 has() {
190 192 return _lifetime.has();
191 193 },
192 194 store(item: any) {
193 195 _lifetime.store(item);
194 196 }
195 197 };
196 198 }
197 199 }
@@ -1,65 +1,66
1 1 import { IDestroyable } from "../interfaces";
2 2 import { ActivationContext } from "./ActivationContext";
3 3 import { LifetimeManager } from "./LifetimeManager";
4 4
5 5 export interface Descriptor<S extends object = any, T = any> {
6 6 activate(context: ActivationContext<S>): T;
7 7 }
8 8
9 9 export type ServiceMap<S extends object> = {
10 10 [k in keyof S]: Descriptor<S, S[k]>;
11 11 };
12 12
13 13 export type ContainerKeys<S extends object> = keyof S | keyof ContainerProvided<S>;
14 14
15 15 export type TypeOfService<S extends object, K> =
16 16 K extends keyof ContainerProvided<S> ? ContainerProvided<S>[K] :
17 17 K extends keyof S ? S[K] : never;
18 18
19 19 export type ContainerServiceMap<S extends object> = {
20 20 [K in ContainerKeys<S>]: Descriptor<S, TypeOfService<S, K>>;
21 21 };
22 22
23 23 export type PartialServiceMap<S extends object> = {
24 24 [k in keyof S]?: Descriptor<S, S[k]>;
25 25 };
26 26
27 27 export interface ServiceLocator<S extends object> {
28 28 resolve<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>): TypeOfService<S, K>;
29 resolve<K extends ContainerKeys<S>>(name: K, def?: undefined): TypeOfService<S, K> | undefined;
29 30 }
30 31
31 32 export interface ServiceContainer<S extends object> extends ServiceLocator<S>, IDestroyable {
32 33 getLifetimeManager(): LifetimeManager;
33 34 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>): this;
34 35 register(services: PartialServiceMap<S>): this;
35 36
36 37 createChildContainer(): ServiceContainer<S>;
37 38 }
38 39
39 40 export interface ContainerProvided<S extends object> {
40 41 container: ServiceLocator<S>;
41 42
42 43 childContainer: ServiceContainer<S>;
43 44 }
44 45
45 46 export type ContainerRegistered<S extends object> = /*{
46 47 [K in Exclude<keyof S, keyof ContainerProvided<S>>]: S[K];
47 48 };*/
48 49 Exclude<S, ContainerProvided<S>>;
49 50
50 51 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
51 52
52 53 /**
53 54 * Интерфейс для управления жизнью экземпляра объекта. Каждая регистрация имеет
54 55 * свой собственный объект `ILifetime`, который создается при первой активации
55 56 */
56 57 export interface ILifetime {
57 58 /** Проверяет, что уже создан экземпляр объекта */
58 59 has(): boolean;
59 60
60 61 get(): any;
61 62
62 63 initialize(context: ActivationContext<any>): void;
63 64
64 65 store(item: any, cleanup?: (item: any) => void): void;
65 66 }
@@ -1,518 +1,520
1 import { ICancellable, Constructor, IDestroyable, ICancellation } from "./interfaces";
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 export function oid(instance: null | undefined): undefined;
9 9 export function oid(instance: NonNullable<any>): string;
10 10 export function oid(instance: any): string | undefined {
11 11 if (isNull(instance))
12 12 return undefined;
13 13
14 14 if (_oid in instance)
15 15 return instance[_oid];
16 16 else
17 17 return (instance[_oid] = "oid_" + (++_nextOid));
18 18 }
19 19
20 20 const cancellationNone: ICancellation = {
21 21 isSupported(): boolean {
22 22 return false;
23 23 },
24 24
25 25 throwIfRequested(): void {
26 26 },
27 27
28 28 isRequested(): boolean {
29 29 return false;
30 30 },
31 31
32 32 register(_cb: (e: any) => void): IDestroyable {
33 33 return destroyed;
34 34 }
35 35 };
36 36
37 37 export function keys<T>(arg: T): (Extract<keyof T, string>)[] {
38 38 return isObject(arg) && arg ? Object.keys(arg) as (Extract<keyof T, string>)[] : [];
39 39 }
40 40
41 41 export function isKeyof<T>(k: string, target: T): k is Extract<keyof T, string> {
42 42 return target && typeof target === "object" && k in target;
43 43 }
44 44
45 45 export function argumentNotNull(arg: any, name: string) {
46 46 if (arg === null || arg === undefined)
47 47 throw new Error("The argument " + name + " can't be null or undefined");
48 48 }
49 49
50 50 export function argumentNotEmptyString(arg: any, name: string) {
51 51 if (typeof (arg) !== "string" || !arg.length)
52 52 throw new Error("The argument '" + name + "' must be a not empty string");
53 53 }
54 54
55 55 export function argumentNotEmptyArray(arg: any, name: string) {
56 56 if (!(arg instanceof Array) || !arg.length)
57 57 throw new Error("The argument '" + name + "' must be a not empty array");
58 58 }
59 59
60 60 export function argumentOfType(arg: any, type: Constructor<{}>, name: string) {
61 61 if (!(arg instanceof type))
62 62 throw new Error("The argument '" + name + "' type doesn't match");
63 63 }
64 64
65 export function isObject(val: any): val is object {
66 return typeof val === "object";
67 }
68
69 65 export function isNull(val: any): val is null | undefined {
70 66 return (val === null || val === undefined);
71 67 }
72 68
73 69 export type primitive = symbol | string | number | boolean | undefined | null;
74 70
75 71 export function isPrimitive(val: any): val is primitive {
76 72 return (val === null || val === undefined || typeof (val) === "string" ||
77 73 typeof (val) === "number" || typeof (val) === "boolean");
78 74 }
79 75
76 export function isObject<T>(value: T): value is Exclude<T & object, primitive> {
77 return !!(value && typeof value === "object");
78 }
79
80 80 export function isInteger(val: any): val is number {
81 81 return parseInt(val, 10) === val;
82 82 }
83 83
84 84 export function isNumber(val: any): val is number {
85 85 return parseFloat(val) === val;
86 86 }
87 87
88 88 export function isString(val: any): val is string {
89 return typeof (val) === "string" || val instanceof String;
89 return typeof (val) === "string";
90 90 }
91 91
92 92 export function isPromise<T = any>(val: any): val is PromiseLike<T> {
93 return val && typeof val.then === "function";
93 return !!(val && typeof val.then === "function");
94 94 }
95 95
96 96 export function isCancellable(val: any): val is ICancellable {
97 return val && typeof val.cancel === "function";
97 return !!(val && typeof val.cancel === "function");
98 98 }
99 99
100 100 export function isNullOrEmptyString(val: any): val is ("" | null | undefined) {
101 101 return (val === null || val === undefined ||
102 102 ((typeof (val) === "string" || val instanceof String) && val.length === 0));
103 103 }
104 104
105 105 export function isNotEmptyArray<T = any>(arg: any): arg is T[] {
106 106 return (arg instanceof Array && arg.length > 0);
107 107 }
108 108
109 109 function _isStrictMode(this: any) {
110 110 return !this;
111 111 }
112 112
113 113 function _getNonStrictGlobal(this: any) {
114 114 return this;
115 115 }
116 116
117 117 export function getGlobal() {
118 118 // in es3 we can't use indirect call to eval, since it will
119 119 // be executed in the current call context.
120 120 if (!_isStrictMode()) {
121 121 return _getNonStrictGlobal();
122 122 } else {
123 123 // tslint:disable-next-line:no-eval
124 124 return eval.call(null, "this");
125 125 }
126 126 }
127 127
128 128 export function get(member: string, context?: object) {
129 129 argumentNotEmptyString(member, "member");
130 130 let that = context || getGlobal();
131 131 const parts = member.split(".");
132 132 for (const m of parts) {
133 133 if (!m)
134 134 continue;
135 135 if (isNull(that = that[m]))
136 136 break;
137 137 }
138 138 return that;
139 139 }
140 140
141 141 /**
142 142 * Выполняет метод для каждого элемента массива, останавливается, когда
143 143 * либо достигнут конец массива, либо функция <c>cb</c> вернула
144 144 * значение.
145 145 *
146 146 * @param {Array | Object} obj массив элементов для просмотра
147 147 * @param {Function} cb функция, вызываемая для каждого элемента
148 148 * @param {Object} thisArg значение, которое будет передано в качестве
149 149 * <c>this</c> в <c>cb</c>.
150 150 * @returns {void}
151 151 */
152 152 export function each<T>(obj: T, cb: <X extends Extract<keyof T, string>>(v: NonNullable<T[X]>, k: X) => void): void;
153 153 export function each<T>(array: T[], cb: (v: T, i: number) => void): void;
154 154 export function each(obj: any, cb: any, thisArg?: any): any;
155 155 export function each(obj: any, cb: any, thisArg?: any) {
156 156 argumentNotNull(cb, "cb");
157 157 if (obj instanceof Array) {
158 158 let v: any;
159 159 for (let i = 0; i < obj.length; i++) {
160 160 v = obj[i];
161 161 if (v !== undefined)
162 162 cb.call(thisArg, v, i);
163 163 }
164 164 } else {
165 165 Object.keys(obj).forEach(k => obj[k] !== undefined && cb.call(thisArg, obj[k], k));
166 166 }
167 167 }
168 168
169 169 /** Copies property values from a source object to the destination and returns
170 170 * the destination object.
171 171 *
172 172 * @param dest The destination object into which properties from the source
173 173 * object will be copied.
174 174 * @param source The source of values which will be copied to the destination
175 175 * object.
176 176 * @param template An optional parameter specifies which properties should be
177 177 * copied from the source and how to map them to the destination. If the
178 178 * template is an array it contains the list of property names to copy from the
179 179 * source to the destination. In case of object the templates contains the map
180 180 * where keys are property names in the source and the values are property
181 181 * names in the destination object. If the template isn't specified then the
182 182 * own properties of the source are entirely copied to the destination.
183 183 *
184 184 */
185 185 export function mixin<T extends object, S extends object>(dest: T, source: S, template?: (keyof S)[]): T & S;
186 186 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;
187 187 export function mixin<T extends object, S extends object>(dest: T, source: S, template?: any): any {
188 188 argumentNotNull(dest, "dest");
189 189 const _res: any = dest as any;
190 190
191 191 if (isPrimitive(source))
192 192 return _res;
193 193
194 194 if (template instanceof Array) {
195 195 template.forEach(p => {
196 196 if (isKeyof(p, source))
197 197 _res[p] = source[p];
198 198 });
199 199 } else if (template) {
200 200 keys(source).forEach(p => {
201 201 if (isKeyof(p, template))
202 202 _res[template[p]] = source[p];
203 203 });
204 204 } else {
205 205 keys(source).forEach(p => _res[p] = source[p]);
206 206 }
207 207
208 208 return _res;
209 209 }
210 210
211 211 /** Wraps the specified function to emulate an asynchronous execution.
212 212 * @param{Object} thisArg [Optional] Object which will be passed as 'this' to the function.
213 213 * @param{Function|String} fn [Required] Function wich will be wrapped.
214 214 */
215 215 export function async<T, F extends (...args: any[]) => T | PromiseLike<T>>(
216 216 fn: F,
217 217 thisArg?: ThisParameterType<F>
218 218 ): (...args: Parameters<F>) => PromiseLike<T>;
219 219 export function async<T, M extends string, O extends { [m in M]?: (...args: any[]) => T | PromiseLike<T> }>(
220 220 fn: M,
221 221 thisArg: O
222 222 ): (...args: Parameters<NonNullable<O[M]>>) => PromiseLike<T>;
223 223 export function async(_fn: any, thisArg: any): (...args: any[]) => PromiseLike<any> {
224 224 let fn = _fn;
225 225
226 226 if (arguments.length === 2 && !(fn instanceof Function))
227 227 fn = thisArg[fn];
228 228
229 229 if (fn == null)
230 230 throw new Error("The function must be specified");
231 231
232 232 function wrapresult(x: any, e?: any): PromiseLike<any> {
233 233 if (e) {
234 234 return {
235 235 then(cb, eb) {
236 236 try {
237 237 return eb ? wrapresult(eb(e)) : this;
238 238 } catch (e2) {
239 239 return wrapresult(null, e2);
240 240 }
241 241 }
242 242 };
243 243 } else {
244 244 if (x && x.then)
245 245 return x;
246 246 return {
247 247 then(cb) {
248 248 try {
249 249 return cb ? wrapresult(cb(x)) : this;
250 250 } catch (e2) {
251 251 return wrapresult(e2);
252 252 }
253 253 }
254 254 };
255 255 }
256 256 }
257 257
258 258 return (...args) => {
259 259 try {
260 260 return wrapresult(fn.apply(thisArg, args));
261 261 } catch (e) {
262 262 return wrapresult(null, e);
263 263 }
264 264 };
265 265 }
266 266
267 267 export function delegate<T extends object, F extends (this: T, ...args: any[]) => any>(
268 268 target: T,
269 269 method: F
270 270 ): OmitThisParameter<F>;
271 271 export function delegate<M extends string, T extends { [m in M]?: (...args: any[]) => any; }>(
272 272 target: T,
273 273 method: M
274 274 ): OmitThisParameter<T[M]>;
275 275 export function delegate(target: any, _method: any): (...args: any[]) => any {
276 276 let method: any;
277 277 if (!(_method instanceof Function)) {
278 278 argumentNotNull(target, "target");
279 279 method = target[_method];
280 280 if (!(method instanceof Function))
281 281 throw new Error("'method' argument must be a Function or a method name");
282 282 } else {
283 283 method = _method;
284 284 }
285 285
286 286 return (...args) => {
287 287 return method.apply(target, args);
288 288 };
289 289 }
290 290
291 291 export function delay(timeMs: number, ct = cancellationNone) {
292 292 ct.throwIfRequested();
293 293 return new Promise((resolve, reject) => {
294 294 const h = ct.register(e => {
295 295 clearTimeout(id);
296 296 reject(e);
297 297 // we don't nedd to unregister h, since ct is already disposed
298 298 });
299 299 const id = setTimeout(() => {
300 300 h.destroy();
301 301 resolve();
302 302 }, timeMs);
303 303
304 304 });
305 305 }
306 306
307 307 /** Returns resolved promise, awaiting this method will cause the asynchronous
308 308 * completion of the rest of the code.
309 309 */
310 310 export function fork() {
311 311 return Promise.resolve();
312 312 }
313 313
314 314 /** Always throws Error, can be used as a stub for the methods which should be
315 315 * assigned later and are required to be not null.
316 316 */
317 317 export function notImplemented(): never {
318 throw new Error("Not implemeted");
318 throw new Error("Not implemented");
319 319 }
320 320 /**
321 321 * Iterates over the specified array of items and calls the callback `cb`, if
322 322 * the result of the callback is a promise the next item from the array will be
323 323 * proceeded after the promise is resolved.
324 324 *
325 325 */
326 326 export function pmap<T, T2>(
327 327 items: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
328 328 cb: (item: T, i: number) => T2 | PromiseLike<T2>
329 329 ): T2[] | PromiseLike<T2[]> {
330 330 argumentNotNull(cb, "cb");
331 331
332 332 if (isPromise(items)) {
333 333 return items.then(data => pmap(data, cb));
334 334 } else {
335 335
336 336 if (isNull(items) || !items.length)
337 337 return [];
338 338
339 339 let i = 0;
340 340 const result = new Array<T2>();
341 341
342 342 const next = (): any => {
343 343 while (i < items.length) {
344 344 const r = cb(items[i], i);
345 345 const ri = i;
346 346 i++;
347 347 if (isPromise(r)) {
348 348 return r.then(x => {
349 349 result[ri] = x;
350 350 return next();
351 351 });
352 352 } else {
353 353 result[ri] = r;
354 354 }
355 355 }
356 356 return result;
357 357 };
358 358
359 359 return next();
360 360 }
361 361 }
362 362
363 363 export function pfor<T>(
364 364 items: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
365 365 cb: (item: T, i: number) => any
366 366 ): void | PromiseLike<void> {
367 367 argumentNotNull(cb, "cb");
368 368
369 369 if (isPromise(items)) {
370 370 return items.then(data => pfor(data, cb));
371 371 } else {
372 372 if (isNull(items) || !items.length)
373 373 return;
374 374
375 375 let i = 0;
376 376
377 377 const next = (): any => {
378 378 while (i < items.length) {
379 379 const r = cb(items[i], i);
380 380 i++;
381 381 if (isPromise(r))
382 382 return r.then(next);
383 383 }
384 384 };
385 385
386 386 return next();
387 387 }
388 388 }
389 389
390 390 export function first<T>(sequence: ArrayLike<T>): T;
391 391 export function first<T>(sequence: PromiseLike<ArrayLike<T>>): PromiseLike<T>;
392 392 export function first<T>(
393 393 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
394 394 cb?: (x: T) => void,
395 395 err?: (x: Error) => void
396 396 ): void;
397 397 /**
398 398 * Выбирает первый элемент из последовательности, или обещания, если в
399 399 * качестве параметра используется обещание, оно должно вернуть массив.
400 400 *
401 401 * @param {Function} cb обработчик результата, ему будет передан первый
402 402 * элемент последовательности в случае успеха
403 403 * @param {Function} err обработчик исключения, если массив пустой, либо
404 404 * не массив
405 405 *
406 406 * @remarks Если не указаны ни cb ни err, тогда функция вернет либо
407 407 * обещание, либо первый элемент.
408 408 * @async
409 409 */
410 410 export function first<T>(
411 411 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
412 412 cb?: (x: T) => void,
413 413 err?: (x: Error) => void
414 414 ) {
415 415 if (isPromise(sequence)) {
416 416 return sequence.then(res => first(res, cb as any /* force to pass undefined cb */, err));
417 417 } else if (sequence && "length" in sequence) {
418 418 if (sequence.length === 0) {
419 419 if (err)
420 420 return err(new Error("The sequence is empty"));
421 421 else
422 422 throw new Error("The sequence is empty");
423 423 } else if (cb) {
424 424 return cb(sequence[0]);
425 425 } else {
426 426 return sequence[0];
427 427 }
428 428 } else {
429 429 if (err)
430 430 return err(new Error("The sequence is required"));
431 431 else
432 432 throw new Error("The sequence is required");
433 433 }
434 434 }
435 435
436 436 export function firstWhere<T>(
437 437 sequence: ArrayLike<T>,
438 438 predicate: (x: T) => boolean
439 439 ): T;
440 440 export function firstWhere<T>(
441 441 sequence: PromiseLike<ArrayLike<T>>,
442 442 predicate: (x: T) => boolean
443 443 ): PromiseLike<T>;
444 444 export function firstWhere<T>(
445 445 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
446 446 predicate: (x: T) => boolean,
447 447 cb: (x: T) => void,
448 448 err?: (x: Error) => void
449 449 ): void;
450 450
451 451 export function firstWhere<T>(
452 452 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
453 453 predicate?: (x: T) => boolean,
454 454 cb?: (x: T) => any,
455 455 err?: (x: Error) => any
456 456 ) {
457 457 if (isPromise(sequence)) {
458 458 return sequence.then(res => firstWhere(
459 459 res,
460 460 predicate as any /* force to pass undefined predicate */,
461 461 cb as any /* force to pass undefined cb */,
462 462 err)
463 463 );
464 464 } else if (sequence && "length" in sequence) {
465 465 if (sequence.length === 0) {
466 466 if (err)
467 467 err(new Error("The sequence is empty"));
468 468 else
469 469 throw new Error("The sequence is empty");
470 470 } else {
471 471 if (!predicate) {
472 472 return cb ? cb(sequence[0]) && void (0) : sequence[0];
473 473 } else {
474 474 for (let i = 0; i < sequence.length; i++) {
475 475 const v = sequence[i];
476 476 if (predicate(v))
477 477 return cb ? cb(v) : v;
478 478 }
479 479 if (err)
480 480 err(new Error("The sequence doesn't contain matching items"));
481 481 else
482 482 throw new Error("The sequence doesn't contain matching items");
483 483 }
484 484 }
485 485 } else {
486 486 if (err)
487 487 err(new Error("The sequence is required"));
488 488 else
489 489 throw new Error("The sequence is required");
490 490 }
491 491 }
492 492
493 493 export function isDestroyable(d: any): d is IDestroyable {
494 if (d && "destroy" in d && typeof (destroy) === "function")
495 return true;
496 return false;
494 return !!(d && typeof d.destroy === "function");
495 }
496
497 export function isRemovable(value: any): value is IRemovable {
498 return !!(value && typeof value.remove === "function");
497 499 }
498 500
499 501 export function destroy(d: any) {
500 if (d && "destroy" in d)
502 if (isDestroyable(d))
501 503 d.destroy();
502 504 }
503 505
504 506 /**
505 507 * Used to mark that the async operation isn't awaited intentionally.
506 508 * @param p The promise which represents the async operation.
507 509 */
508 510 export function nowait(p: Promise<any>) {
509 511 }
510 512
511 513 /** represents already destroyed object.
512 514 */
513 515 export const destroyed = {
514 516 /** Calling to this method doesn't affect anything, noop.
515 517 */
516 518 destroy() {
517 519 }
518 520 };
@@ -1,12 +1,12
1 1 {
2 2 "compilerOptions": {
3 3 "moduleResolution": "node",
4 4 "experimentalDecorators": true,
5 5 "noEmitOnError": true,
6 6 "listFiles": true,
7 //"strict": true,
7 "strict": true,
8 8 "types": [],
9 9 "target": "ES5",
10 10 "lib": ["es5", "es2015.promise", "es2015.symbol", "es2015.iterable", "dom", "scripthost"]
11 11 }
12 12 } No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now