##// END OF EJS Templates
Merged in propose cancellations (pull request #1)...
m407 -
r21:dd8f8dfcd934 merge default
parent child
Show More
@@ -0,0 +1,1
1 *.tgz No newline at end of file
@@ -0,0 +1,25
1 @startuml
2
3 participant Component as a
4 participant Other as b
5
6 [-> a : activate(ct)
7 activate a
8 <-- a : promise
9 a -> a : onActivating(ct)
10 activate a
11 a -> b : doAsyncWork(ct)
12 deactivate a
13 deactivate a
14 activate b
15
16 [-> b : ct.cancel
17 b --> a : reject(Cancelled)
18 deactivate b
19 activate a
20
21 a -> a : setFailState()
22
23 [<-- a : reject(Cancelled)
24
25 @enduml No newline at end of file
@@ -0,0 +1,257
1 # Cancellations. Отмена асинхронных операций
2
3 Использование Promise позволяет организовать обработку результатов работы
4 асинхронных фукнций. Ключевые слова async/await позволяют работать с
5 асинхронными вызовами в стиле процедурного программирования, хотя по сути это
6 событиный подход. При всей своей красоте даннго подхода в нем умышленно
7 отсутсвует механизм отмены асинхронной операции, т.е. ее можно начать, но нельзя
8 отказаться от результатов ее выполнения, даже если это уже не требуется.
9
10 Примером того, когда может потребоваться отмена является загрузка большого
11 файла, при которой пользователю отображается окно хода операции с возможностью
12 ее отмены.
13
14 ```ts
15 // имеется некоторый HTTP клиент
16 let client = new HttpClient();
17
18 // загружается большой файл, с использованием медленного канала
19 let data = await client.getJsonAsync('http://host/large-file.json');
20
21 ```
22
23 Как поступить в данной ситуации, прежде всего нужно, чтобы сама операция
24 поддерживала возможность отмены, предположим, что для этого есть метод
25 `client.abort()`.
26
27 ```ts
28
29 // имеется некоторый HTTP клиент
30 let client = new HttpClient();
31
32 // отображаем окно с информацией о ходе операции
33 let progressView = showProgress("Downloading, please wait...");
34
35 // код оборачивается в try/finally поскольку созданную форму нужно закрыть
36 try {
37 // загружается большой файл, с использованием медленного канала
38 // здесь, в отличии от предыдущего примера, мы не дожидаемся результата,
39 // а запоминаем обещание в переменную downloadTask
40 let downloadTask = client.getJsonAsync('http://host/large-file.json');
41
42 // связываем событие нажатия кнопки с отменой загрузки
43 progressView.once('cancel', () => client.abort());
44
45 // ожидаем окончания загрузки данных
46
47 let data = await downloadTask;
48 } finally {
49 // независимот от результата закрываем форму
50 // при этом также происходит ануллирование подписок на события
51 progressView.close();
52 }
53
54 ```
55
56 Технические приведенное решение выглядит не плохо, но проблемы появляются, когда
57 требуется организовать отмену нескольких операций, особенно если они вложенные.
58
59 ```ts
60 // обновление информации о человеке на форме
61 async function updatePersonInfo(info) {
62 let client = new RestApiClient();
63
64 // выплнение нескольких асинхронных операций
65 let org = await client.getOrgAsync(info.orgId);
66 let city = await client.getCityAsync(info.cityId);
67
68 // обновление содержимого представления
69 renderContent({
70 person: info,
71 org: org,
72 city: city
73 });
74 }
75
76 ```
77
78 Чтобы реализовать возможность отмены такой операции требуется, чтобы в логике
79 самой операции была реализована поддержка отмены. Для реализации этого
80 потребуется чтобы у операции была информация о запросе отмены, причем данная
81 информация относится именно к текущей операции.
82
83 Информацию о состоянии запроса на отмену назовём **маркер отмены (cancellation
84 token)**. Поскольку маркер отмены тесно связан с операцией, его удобно
85 передавать в виде параметра, тогда код операции будет выглядеть так:
86
87 ```ts
88 // обновление информации о человеке на форме
89 // ct - маркер отмены
90 async function updatePersonInfo(info, ct) {
91 let client = new RestApiClient();
92
93 // выплнение нескольких асинхронных операций
94 // маркер отмены просто передается далее по цепочке вызовов, без
95 // дополнительных действий
96 let org = await client.getOrg(info.orgId, ct);
97 let city = await client.getCity(info.cityId, ct);
98
99 // обновление содержимого представления
100 renderContent({
101 person: info,
102 org: org,
103 city: city
104 });
105 }
106
107 ///////////////////////////////////////////////////////////////////////////////
108 // ... где-то в другом месте
109 ///////////////////////////////////////////////////////////////////////////////
110
111 // отображаем окно с информацией о хоже операции
112 let progressView = showProgress("Loading, please wait...");
113
114 // создаем маркер отмены для операции на основе события 'cancel'.
115 let ct = new Cancellation(cancel => progressView.on('cancel', cancel));
116
117 // код оборачивается в try/finally поскольку созданную форму нужно закрыть
118 try {
119 // асинхронно получаем информацию о человеке
120 let data = await getPersonInfo(personId, ct);
121
122 // асинхронно обновляем представление
123 await updatePersonInfo(data, ct);
124 } finally {
125 // независимот от результата закрываем форму
126 // при этом также происходит ануллирование подписок на события
127 progressView.close();
128 }
129
130 ```
131
132 Таким образом тот, кто начинает асинхронную операцию заранее определяет как эта
133 опреция будет отменена.
134
135 Важно понимать, что для реализации отмены операции
136 могут выделаться ресурсы требующие явного освобождения (DOM, таймеры, события),
137 об их освобождении по окончанию операции (успешном или нет) должен позаботиться
138 инициатор этой операции. `Cancellation` выступает только в роли посредника для
139 доставки события отмены операции до конечного получателя, он не отслеживает и
140 не освобождает ресурсы, кроме того, асинхронная операция может его попросту
141 проигнорировать.
142
143 ## `ICancellation` Маркер отмены операции
144
145 Интерфейс маркера отмены операции. Используется асинхронными операциями, чтобы
146 получить оповещение о требуемой отмене.
147
148 ### `isSupported(): boolean`
149
150 Определяет, может ли быть запрошена отмена операции через данный маркер.
151
152 ### `isRequested(): boolean`
153
154 Возвращает текущее состояние запроса на отмену.
155
156 ### `throwIfRequested(): void`
157
158 Если отмена была запрошена, бросает в качестве исключения причину отмены.
159
160 ### `register(cb: (e:any) => void): IDestroyable`
161
162 Метод, зарегистрировать обработчик на запрос отмены. Если отмена была запрошена
163 зарегистрированный обработчик будет вызван ровно один раз, независимо от того,
164 был ли он зарегистрирован до или после запроса отмены.
165
166 Если отмена уже была запрошена, обработчик будет вызван сразу при регистрации,
167 при этом исключения, которые могу возникнуть в обработчике не будут обработаны,
168 а передадуться наверх.
169
170 Вызов данного метода приводит к выделению ресурсов, поэтому операция,
171 зарегистрировавшая обработчик должна освободить подписку, которую вернет метод.
172
173 ```ts
174 async function getAsync(url: string, ct: ICancellation = Cancellation.none) {
175 // переменная в которой будет запомнена подписка на запрос отмены
176 let reg;
177 try {
178 // оборачиваем операцию загрузки в Promise
179 return await new Promise<string>((resolve, reject) => {
180 // объект Xhr
181 const xhr = new XMLHttpRequest();
182 xhr.open("GET", url);
183
184 // регистрируем обработчики Promise
185 xhr.onload = () => resolve(xhr.responseText);
186 xhr.onerror = () => reject(xhr.statusText);
187
188 // отправляем запрос
189 xhr.send();
190
191 // подписываемся на запрос отмены
192 reg = ct.register((e) => {
193 reject(e);
194 xhr.abort();
195 });
196 });
197 } finally {
198 if (reg)
199 reg.destroy();
200 }
201 }
202
203 ```
204
205 Использование метода `register()` предполагается для организации отмены операций
206 не поддерживающих маркеры отмены.
207
208 ## `Cancellation` Источник отмены
209
210 Класс используется для создания маркеров отмены. Позволяет создать маркер при
211 начале асинхронной операции и связать его, например, с событием DOM.
212
213 Также маркер можно создавать, когда требуется сложное условие отмены текущей и
214 всех нижележещих операций.
215
216 Как правило в большинстве операций достаточно маркера переданного в параметрах,
217 этот же маркер может передаваться ниже.
218
219 ### `constructor(exec: (cancel: (reason:any) => void ) => void )`
220
221 Создает новый маркер, при помощи параметра и инициализирует его при помощи
222 фукнции, переданной в параметре `exec`.
223
224 ```ts
225
226 let htimer;
227 let ct = new Cancellation(cancel => {
228 htimer = setTimeout(() => cancel("The request is timed out."), 1000);
229 });
230
231 try {
232 let text = await getAsync(url, ct);
233 } finally {
234 // инициатор должен освобождать ресурсы
235 // передача недействительного htimer не приводит ни к каким последствиям
236 clearTimeout(htimer);
237 }
238
239 ```
240
241 ## `Cancellation.none: ICancellation`
242
243 Статическое свойство только для чтения, в котором находится специальный токен
244 запроса отмены. Этот токен означает, что отмена никогда не может произойти.
245
246 Данный токен рекомендуется использовать как значение по-умолчанию для
247 параметров, принимающих токен отмены.
248
249 ```ts
250
251 async function load(url: string, ct: ICancellation = Cancellation.none) {
252 ct.throwIfRequested();
253
254 // ... the rest of method
255 }
256
257 ``` No newline at end of file
@@ -0,0 +1,89
1 import { ICancellation, IDestroyable } from "./interfaces";
2 import { argumentNotNull } from "./safe";
3
4 const destroyed = {
5 destroy() {
6 }
7 };
8
9 export class Cancellation implements ICancellation {
10 private _reason: any;
11 private _cbs: Array<(e) => void>;
12
13 constructor(action: (cancel: (e) => void) => void) {
14 argumentNotNull(action, "action");
15
16 action(this._cancel.bind(this));
17 }
18
19 isSupported(): boolean {
20 return true;
21 }
22 throwIfRequested(): void {
23 if (this._reason)
24 throw this._reason;
25 }
26
27 isRequested(): boolean {
28 return !!this._reason;
29 }
30
31 register(cb: (e: any) => void): IDestroyable {
32 argumentNotNull(cb, "cb");
33
34 if (this._reason) {
35 cb(this._reason);
36 return destroyed;
37 } else {
38 if (!this._cbs)
39 this._cbs = [cb];
40 else
41 this._cbs.push(cb);
42
43 let me = this;
44 return {
45 destroy() {
46 me._unregister(cb);
47 }
48 };
49 }
50 }
51
52 private _unregister(cb) {
53 if(this._cbs) {
54 let i = this._cbs.indexOf(cb);
55 if ( i>=0 )
56 this._cbs.splice(i,1);
57 }
58 }
59
60 private _cancel(reason) {
61 if (this._reason)
62 return;
63
64 this._reason = (reason = reason || new Error("Operation cancelled"));
65
66
67 if (this._cbs) {
68 this._cbs.forEach(cb => cb(reason));
69 this._cbs = null;
70 }
71 }
72
73 static readonly none: ICancellation = {
74 isSupported(): boolean {
75 return false;
76 },
77
78 throwIfRequested(): void {
79 },
80
81 isRequested(): boolean {
82 return false;
83 },
84
85 register(_cb: (e: any) => void): IDestroyable {
86 return destroyed;
87 }
88 };
89 } No newline at end of file
@@ -0,0 +1,87
1 import { IActivationController, IActivatable, ICancellation } from '../interfaces';
2 import { AsyncComponent } from './AsyncComponent';
3 import { Cancellation } from '../Cancellation';
4 import * as TraceSource from '../log/TraceSource';
5
6 type Constructor<T = {}> = new (...args: any[]) => T;
7
8 const log = TraceSource.get('@implab/core/components/ActivatableMixin');
9
10 function ActivatableMixin<TBase extends Constructor<AsyncComponent>>(Base: TBase) {
11 return class extends Base implements IActivatable {
12 _controller: IActivationController;
13
14 _active: boolean;
15
16 isActive() {
17 return this._active;
18 }
19
20 getActivationController() {
21 return this._controller;
22 }
23
24 setActivationController(controller: IActivationController) {
25 this._controller = controller;
26 }
27
28 async onActivating(ct: ICancellation) {
29 if (this._controller)
30 await this._controller.activating(this, ct);
31 }
32
33 async onActivated(ct: ICancellation) {
34 if (this._controller)
35 await this._controller.activated(this, ct);
36 }
37
38 activate(ct: ICancellation = Cancellation.none) {
39 return this.runOperation(this._activateAsync.bind(this), ct);
40 }
41
42 async _activateAsync(ct: ICancellation) {
43 if (this.isActive())
44 return;
45
46 await this.onActivating(ct);
47 this._active = true;
48 try {
49 await this.onActivated(ct);
50 } catch (e) {
51 log.error("Suppressed onActivated error: {0}", e);
52 }
53 }
54
55 async onDeactivating(ct: ICancellation) {
56 if (this._controller)
57 await this._controller.deactivating(this, ct);
58 }
59
60 async onDeactivated(ct: ICancellation) {
61 if (this._controller)
62 await this._controller.deactivated(this, ct);
63 }
64
65 deactivate(ct: ICancellation = Cancellation.none) {
66 return this.runOperation(this._deactivateAsync.bind(this), ct);
67 }
68
69 async _deactivateAsync(ct: ICancellation) {
70 if (!this.isActive())
71 return;
72 await this.onDeactivating(ct);
73 this._active = false;
74 try {
75 await this.onDeactivated(ct);
76 } catch (e) {
77 log.error("Suppressed onDeactivated error: {0}", e);
78 }
79 }
80 }
81 }
82
83 namespace ActivatableMixin {
84 export const traceSource = log;
85 }
86
87 export = ActivatableMixin; No newline at end of file
@@ -0,0 +1,17
1 import { Cancellation } from "../Cancellation";
2 import { IAsyncComponent, ICancellation } from "../interfaces";
3
4 export class AsyncComponent implements IAsyncComponent {
5 _completion: Promise<void> = Promise.resolve();
6
7 getCompletion() { return this._completion };
8
9 runOperation(op: (ct: ICancellation) => any, ct: ICancellation = Cancellation.none) {
10 // TODO create cancellation source here
11 async function guard() {
12 await op(ct);
13 }
14
15 return this._completion = guard();
16 }
17 } No newline at end of file
@@ -0,0 +1,198
1 import { IObservable, IDestroyable, ICancellation } from '../interfaces';
2 import { Cancellation } from '../Cancellation'
3 import { argumentNotNull } from '../safe';
4
5
6 interface Handler<T> {
7 (x: T): void
8 }
9
10 interface Initializer<T> {
11 (notify: Handler<T>, error?: (e: any) => void, complete?: () => void): void;
12 }
13
14 // TODO: think about to move this interfaces.ts and make it public
15 interface IObserver<T> {
16 next(event: T): void
17
18 error(e: any): void
19
20 complete(): void
21 }
22
23 const noop = () => {};
24
25 class Observable<T> implements IObservable<T> {
26 private _once = new Array<IObserver<T>>();
27
28 private _observers = new Array<IObserver<T>>();
29
30
31 private _complete: boolean
32
33 private _error: any
34
35 constructor(func?: Initializer<T>) {
36 if (func)
37 func(
38 this._notifyNext.bind(this),
39 this._notifyError.bind(this),
40 this._notifyCompleted.bind(this)
41 );
42 }
43
44 /**
45 * Registers handlers for the current observable object.
46 *
47 * @param next the handler for events
48 * @param error the handler for a error
49 * @param complete the handler for a completion
50 * @returns {IDestroyable} the handler for the current subscription, this
51 * handler can be used to unsubscribe from events.
52 *
53 */
54 on(next: Handler<T>, error?: Handler<any>, complete?: () => void): IDestroyable {
55 argumentNotNull(next, "next");
56
57 let me = this;
58
59 let observer: IObserver<T> & IDestroyable = {
60 next: next,
61 error: error ? error.bind(null) : noop,
62 complete: complete ? complete.bind(null) : noop,
63
64 destroy() {
65 me._removeObserver(this);
66 }
67 }
68
69 this._addObserver(observer);
70
71
72 return observer;
73 }
74
75 private _addObserver(observer: IObserver<T>) {
76 if (this._complete) {
77 try {
78 if (this._error)
79 observer.error(this._error);
80 else
81 observer.complete();
82 } catch (e) {
83 this.onObserverException(e);
84 }
85 } else {
86 this._observers.push(observer);
87 }
88 }
89
90 /**
91 * Waits for the next event. This method can't be used to read messages
92 * as a sequence since it can skip some messages between calls.
93 *
94 * @param ct a cancellation token
95 */
96 next(ct: ICancellation = Cancellation.none): Promise<T> {
97 return new Promise<T>((resolve, reject) => {
98 let observer: IObserver<T> = {
99 next: resolve,
100 error: reject,
101 complete: () => reject("No more events are available")
102 };
103
104 if (this._addOnce(observer) && ct.isSupported()) {
105 ct.register((e) => {
106 this._removeOnce(observer);
107 reject(e);
108 });
109 }
110 });
111 }
112
113 private _addOnce(observer: IObserver<T>) {
114 if (this._complete) {
115 try {
116 if (this._error)
117 observer.error(this._error);
118 else
119 observer.complete();
120 } catch (e) {
121 this.onObserverException(e);
122 }
123 return false;
124 }
125
126 this._once.push(observer);
127 return true;
128 }
129
130 protected onObserverException(e: any) {
131 }
132
133 private _removeOnce(d: IObserver<T>) {
134 let i = this._once.indexOf(d);
135 if (i >= 0)
136 this._once.splice(i, 1);
137 }
138
139 private _removeObserver(d: IObserver<T>) {
140 let i = this._observers.indexOf(d);
141 if (i >= 0)
142 this._observers.splice(i, 1);
143 }
144
145 private _notify(guard: (observer: IObserver<T>) => void) {
146 if (this._once.length) {
147 for (let i = 0; i < this._once.length; i++)
148 guard(this._once[i]);
149 this._once = [];
150 }
151
152 for (let i = 0; i < this._observers.length; i++)
153 guard(this._observers[i]);
154 }
155
156 protected _notifyNext(evt: T) {
157 let guard = (observer: IObserver<T>) => {
158 try {
159 observer.next(evt);
160 } catch (e) {
161 this.onObserverException(e);
162 }
163 }
164
165 this._notify(guard);
166 }
167
168 protected _notifyError(e: any) {
169 let guard = (observer: IObserver<T>) => {
170 try {
171 observer.error(e);
172 } catch (e) {
173 this.onObserverException(e);
174 }
175 }
176
177 this._notify(guard);
178 this._observers = [];
179 }
180
181 protected _notifyCompleted() {
182 let guard = (observer: IObserver<T>) => {
183 try {
184 observer.complete();
185 } catch (e) {
186 this.onObserverException(e);
187 }
188 }
189
190 this._notify(guard);
191 this._observers = [];
192 }
193 }
194
195 namespace Observable {
196 }
197
198 export = Observable; No newline at end of file
@@ -0,0 +1,76
1 export interface IDestroyable {
2 destroy();
3 }
4
5 export interface ICancellation {
6 throwIfRequested(): void;
7 isRequested(): boolean;
8 isSupported(): boolean;
9 register(cb: (e: any) => void): IDestroyable;
10 }
11
12 /**
13 * Интерфейс поддерживающий асинхронную активацию
14 */
15 export interface IActivatable {
16 /**
17 * @returns Boolean indicates the current state
18 */
19 isActive(): boolean;
20
21 /**
22 * Starts the component activation
23 * @param ct cancellation token for this operation
24 */
25 activate(ct?: ICancellation) : Promise<void>;
26
27 /**
28 * Starts the component deactivation
29 * @param ct cancellation token for this operation
30 */
31 deactivate(ct?: ICancellation) : Promise<void>;
32
33 /**
34 * Sets the activation controller for this component
35 * @param controller The activation controller
36 *
37 * Activation controller checks whether this component
38 * can be activated and manages the active state of the
39 * component
40 */
41 setActivationController(controller: IActivationController);
42
43 /**
44 * Gets the current activation controller for this component
45 */
46 getActivationController(): IActivationController;
47 }
48
49 export interface IActivationController {
50 activating(component: IActivatable, ct?: ICancellation): Promise<void>;
51
52 activated(component: IActivatable, ct?: ICancellation): Promise<void>;
53
54 deactivating(component: IActivatable, ct?: ICancellation): Promise<void>;
55
56 deactivated(component: IActivatable, ct?: ICancellation): Promise<void>;
57
58 deactivate(ct?: ICancellation): Promise<void>;
59
60 activate(component: IActivatable, ct?: ICancellation): Promise<void>;
61
62 getActive(): IActivatable;
63 }
64
65 export interface IAsyncComponent {
66 getCompletion(): Promise<void>;
67 }
68
69 export interface ICancellable {
70 cancel(reason?: any): void;
71 }
72
73 export interface IObservable<T> {
74 on(next: (x:T) => void, error?: (e:any) => void, complete?:() => void): IDestroyable;
75 next(ct?: ICancellation) : Promise<T>;
76 } No newline at end of file
@@ -0,0 +1,21
1 import * as TraceSource from './TraceSource'
2
3 class TraceEvent {
4 readonly source: TraceSource;
5
6 readonly level: Number;
7
8 readonly arg: any;
9
10 constructor(source: TraceSource, level: Number, arg: any) {
11 this.source = source;
12 this.level = level;
13 this.arg = arg;
14 }
15 }
16
17 namespace TraceEvent {
18
19 }
20
21 export = TraceEvent No newline at end of file
@@ -0,0 +1,171
1 import * as format from '../text/format'
2 import { argumentNotNull } from '../safe';
3 import * as Observable from '../components/Observable'
4 import { IDestroyable } from '../interfaces';
5 import * as TraceEvent from './TraceEvent'
6
7 class Registry {
8 static readonly instance = new Registry();
9
10 private _registry: object = new Object();
11 private _listeners: object = new Object();
12 private _nextCookie: number = 1;
13
14 get(id: any): TraceSource {
15 argumentNotNull(id, "id");
16
17 if (this._registry[id])
18 return this._registry[id];
19
20 var source = new TraceSource(id);
21 this._registry[id] = source;
22 this._onNewSource(source);
23
24 return source;
25 }
26
27 add(id: any, source: TraceSource) {
28 argumentNotNull(id, "id");
29 argumentNotNull(source, "source");
30
31 this._registry[id] = source;
32 this._onNewSource(source);
33 }
34
35 _onNewSource(source: TraceSource) {
36 for (let i in this._listeners)
37 this._listeners[i].call(null, source);
38 }
39
40 on(handler: (source: TraceSource) => void): IDestroyable {
41 argumentNotNull(handler, "handler");
42 var me = this;
43
44 var cookie = this._nextCookie++;
45
46 this._listeners[cookie] = handler;
47
48 for (let i in this._registry)
49 handler(this._registry[i]);
50
51 return {
52 destroy() {
53 delete me._listeners[cookie];
54 }
55 };
56 }
57 }
58
59 class TraceSource extends Observable<TraceEvent> {
60 readonly id: any
61
62 level: number
63
64 constructor(id: any) {
65 super();
66 this.id = id || new Object();
67 }
68
69 protected emit(level: number, arg: any) {
70 this._notifyNext(new TraceEvent(this, level, arg));
71 }
72
73 isDebugEnabled() {
74 return this.level >= TraceSource.DebugLevel;
75 }
76
77 debug(msg: string, ...args: any[]) {
78 if (this.isEnabled(TraceSource.DebugLevel))
79 this.emit(TraceSource.DebugLevel, format(msg, args));
80 }
81
82 isLogEnabled() {
83 return this.level >= TraceSource.LogLevel;
84 }
85
86 log(msg: string, ...args: any[]) {
87 if (this.isEnabled(TraceSource.LogLevel))
88 this.emit(TraceSource.LogLevel, format(msg, args));
89 }
90
91 isWarnEnabled() {
92 return this.level >= TraceSource.WarnLevel;
93 }
94
95 warn(msg: string, ...args: any[]) {
96 if (this.isEnabled(TraceSource.WarnLevel))
97 this.emit(TraceSource.WarnLevel, format(msg, args));
98 }
99
100 /**
101 * returns true if errors will be recorded.
102 */
103 isErrorEnabled() {
104 return this.level >= TraceSource.ErrorLevel;
105 }
106
107 /**
108 * Traces a error.
109 *
110 * @param msg the message.
111 * @param args parameters which will be substituted in the message.
112 */
113 error(msg: string, ...args: any[]) {
114 if (this.isEnabled(TraceSource.ErrorLevel))
115 this.emit(TraceSource.ErrorLevel, format(msg, args));
116 }
117
118 /**
119 * Checks whether the specified level is enabled for this
120 * trace source.
121 *
122 * @param level the trace level which should be checked.
123 */
124 isEnabled(level: number) {
125 return (this.level >= level);
126 }
127
128 /**
129 * Traces a raw event, passing data as it is to the underlying listeners
130 *
131 * @param level the level of the event
132 * @param arg the data of the event, can be a simple string or any object.
133 */
134 traceEvent(level: number, arg: any) {
135 if (this.isEnabled(level))
136 this.emit(level, arg);
137 }
138
139 /**
140 * Register the specified handler to be called for every new and already
141 * created trace source.
142 *
143 * @param handler the handler which will be called for each trace source
144 */
145 static on(handler: (source: TraceSource) => void) {
146 return Registry.instance.on(handler);
147 }
148
149 /**
150 * Creates or returns already created trace source for the specified id.
151 *
152 * @param id the id for the trace source
153 */
154 static get(id: any) {
155 return Registry.instance.get(id);
156 }
157 }
158
159 namespace TraceSource {
160 export const DebugLevel = 400;
161
162 export const LogLevel = 300;
163
164 export const WarnLevel = 200;
165
166 export const ErrorLevel = 100;
167
168 export const SilentLevel = 0;
169 }
170
171 export = TraceSource; No newline at end of file
@@ -0,0 +1,35
1 import { IObservable, IDestroyable, ICancellation } from "../../interfaces";
2 import * as TraceEvent from '../TraceEvent';
3 import { Cancellation } from "../../Cancellation";
4 import * as TraceSource from "../TraceSource";
5
6 class ConsoleWriter implements IDestroyable {
7 readonly _subscriptions = new Array<IDestroyable>();
8
9 writeEvents(source: IObservable<TraceEvent>, ct: ICancellation = Cancellation.none) {
10 var subscription = source.on(this.writeEvent.bind(this));
11 if (ct.isSupported()) {
12 ct.register(subscription.destroy.bind(subscription));
13 }
14 this._subscriptions.push(subscription);
15 }
16
17 writeEvent(next: TraceEvent) {
18 if (next.level >= TraceSource.LogLevel) {
19 console.log(next.source.id.toString(), next.arg);
20 } else if(next.level >= TraceSource.WarnLevel) {
21 console.warn(next.source.id.toString(), next.arg);
22 } else {
23 console.error(next.source.id.toString(), next.arg);
24 }
25 }
26
27 destroy() {
28 this._subscriptions.forEach(x => x.destroy());
29 }
30 }
31
32 namespace ConsoleWriter {
33 }
34
35 export = ConsoleWriter; No newline at end of file
@@ -0,0 +1,231
1 export function argumentNotNull(arg, name) {
2 if (arg === null || arg === undefined)
3 throw new Error("The argument " + name + " can't be null or undefined");
4 }
5
6 export function argumentNotEmptyString(arg, name) {
7 if (typeof (arg) !== "string" || !arg.length)
8 throw new Error("The argument '" + name + "' must be a not empty string");
9 }
10
11 export function argumentNotEmptyArray(arg, name) {
12 if (!(arg instanceof Array) || !arg.length)
13 throw new Error("The argument '" + name + "' must be a not empty array");
14 }
15
16 export function argumentOfType(arg, type, name) {
17 if (!(arg instanceof type))
18 throw new Error("The argument '" + name + "' type doesn't match");
19 }
20
21 export function isNull(arg) {
22 return (arg === null || arg === undefined);
23 }
24
25 export function isPrimitive(arg) {
26 return (arg === null || arg === undefined || typeof (arg) === "string" ||
27 typeof (arg) === "number" || typeof (arg) === "boolean");
28 }
29
30 export function isInteger(arg) {
31 return parseInt(arg) == arg;
32 }
33
34 export function isNumber(arg) {
35 return parseFloat(arg) == arg;
36 }
37
38 export function isString(val) {
39 return typeof (val) == "string" || val instanceof String;
40 }
41
42 export function isNullOrEmptyString(str) {
43 if (str === null || str === undefined ||
44 ((typeof (str) == "string" || str instanceof String) && str.length === 0))
45 return true;
46 }
47
48 export function isNotEmptyArray(arg) {
49 return (arg instanceof Array && arg.length > 0);
50 }
51
52 /**
53 * Выполняет метод для каждого элемента массива, останавливается, когда
54 * либо достигнут конец массива, либо функция <c>cb</c> вернула
55 * значение.
56 *
57 * @param {Array | Object} obj массив элементов для просмотра
58 * @param {Function} cb функция, вызываемая для каждого элемента
59 * @param {Object} thisArg значение, которое будет передано в качестве
60 * <c>this</c> в <c>cb</c>.
61 * @returns Результат вызова функции <c>cb</c>, либо <c>undefined</c>
62 * если достигнут конец массива.
63 */
64 export function each(obj, cb, thisArg) {
65 argumentNotNull(cb, "cb");
66 var i, x;
67 if (obj instanceof Array) {
68 for (i = 0; i < obj.length; i++) {
69 x = cb.call(thisArg, obj[i], i);
70 if (x !== undefined)
71 return x;
72 }
73 } else {
74 var keys = Object.keys(obj);
75 for (i = 0; i < keys.length; i++) {
76 var k = keys[i];
77 x = cb.call(thisArg, obj[k], k);
78 if (x !== undefined)
79 return x;
80 }
81 }
82 }
83
84 /** Wraps the specified function to emulate an asynchronous execution.
85 * @param{Object} thisArg [Optional] Object which will be passed as 'this' to the function.
86 * @param{Function|String} fn [Required] Function wich will be wrapped.
87 */
88 export function async(_fn: (...args: any[]) => any, thisArg) : (...args: any[]) => PromiseLike<any> {
89 let fn = _fn;
90
91 if (arguments.length == 2 && !(fn instanceof Function))
92 fn = thisArg[fn];
93
94 if (fn == null)
95 throw new Error("The function must be specified");
96
97 function wrapresult(x, e?) : PromiseLike<any> {
98 if (e) {
99 return {
100 then: function (cb, eb) {
101 try {
102 return eb ? wrapresult(eb(e)) : this;
103 } catch (e2) {
104 return wrapresult(null, e2);
105 }
106 }
107 };
108 } else {
109 if (x && x.then)
110 return x;
111 return {
112 then: function (cb) {
113 try {
114 return cb ? wrapresult(cb(x)) : this;
115 } catch (e2) {
116 return wrapresult(e2);
117 }
118 }
119 };
120 }
121 }
122
123 return function () {
124 try {
125 return wrapresult(fn.apply(thisArg, arguments));
126 } catch (e) {
127 return wrapresult(null, e);
128 }
129 };
130 }
131
132 export function delegate(target, _method: (string | Function)) {
133 let method : Function;
134
135 if (!(_method instanceof Function)) {
136 argumentNotNull(target, "target");
137 method = target[_method];
138 } else {
139 method = _method;
140 }
141
142 if (!(method instanceof Function))
143 throw new Error("'method' argument must be a Function or a method name");
144
145 return function () {
146 return method.apply(target, arguments);
147 };
148 }
149
150 /**
151 * Для каждого элемента массива вызывает указанную функцию и сохраняет
152 * возвращенное значение в массиве результатов.
153 *
154 * @remarks cb может выполняться асинхронно, при этом одновременно будет
155 * только одна операция.
156 *
157 * @async
158 */
159 export function pmap(items, cb) {
160 argumentNotNull(cb, "cb");
161
162 if (items && items.then instanceof Function)
163 return items.then(function (data) {
164 return pmap(data, cb);
165 });
166
167 if (isNull(items) || !items.length)
168 return items;
169
170 var i = 0,
171 result = [];
172
173 function next() {
174 var r, ri;
175
176 function chain(x) {
177 result[ri] = x;
178 return next();
179 }
180
181 while (i < items.length) {
182 r = cb(items[i], i);
183 ri = i;
184 i++;
185 if (r && r.then) {
186 return r.then(chain);
187 } else {
188 result[ri] = r;
189 }
190 }
191 return result;
192 }
193
194 return next();
195 }
196
197 /**
198 * Выбирает первый элемент из последовательности, или обещания, если в
199 * качестве параметра используется обещание, оно должно вернуть массив.
200 *
201 * @param {Function} cb обработчик результата, ему будет передан первый
202 * элемент последовательности в случае успеха
203 * @param {Function} err обработчик исключения, если массив пустой, либо
204 * не массив
205 *
206 * @remarks Если не указаны ни cb ни err, тогда функция вернет либо
207 * обещание, либо первый элемент.
208 * @async
209 */
210 export function first(sequence: any, cb: Function, err: Function) {
211 if (sequence) {
212 if (sequence.then instanceof Function) {
213 return sequence.then(function (res) {
214 return first(res, cb, err);
215 }, err);
216 } else if (sequence && "length" in sequence) {
217 if (sequence.length === 0) {
218 if (err)
219 return err(new Error("The sequence is empty"));
220 else
221 throw new Error("The sequence is empty");
222 }
223 return cb ? cb(sequence[0]) : sequence[0];
224 }
225 }
226
227 if (err)
228 return err(new Error("The sequence is required"));
229 else
230 throw new Error("The sequence is required");
231 } No newline at end of file
@@ -0,0 +1,7
1 declare function format(format: string, ...args: any[]): string;
2
3 declare namespace format {
4
5 }
6
7 export = format; No newline at end of file
@@ -0,0 +1,29
1 define(["tape"], function(tape) {
2 "use strict";
3 var sourceId = '73a633f3-eab8-49b0-8601-07cae710f234';
4 var sourceId2 = '3ba9c7cd-ed77-437b-9a2f-1cbeb1226b5b';
5 tape('Load TraceSource for the module', function(t) {
6 require(["core/log/trace!" + sourceId, "core/log/TraceSource"], function(trace, TraceSource) {
7 t.equal(trace && trace.id, sourceId, "trace should be taken from the loader plugin parameter");
8
9 var count = 0;
10
11 var h = TraceSource.on(function(x) {
12 if(x.id == sourceId || x.id == sourceId2)
13 count++;
14 });
15
16 t.equal(count, 1, "should see created channel immediatelly");
17 t.equal(trace, TraceSource.get(sourceId), "should get same TraceSource from registry");
18 t.equal(count, 1);
19
20 TraceSource.get(sourceId2);
21
22 t.equal(count, 2);
23
24 h.destroy();
25
26 t.end();
27 });
28 });
29 }); No newline at end of file
@@ -0,0 +1,108
1 import * as tape from 'tape';
2 import * as ActivatableMixin from '@implab/core/components/ActivatableMixin';
3 import { AsyncComponent } from '@implab/core/components/AsyncComponent';
4 import { IActivationController, IActivatable, ICancellation } from '@implab/core/interfaces';
5 import { Cancellation } from '@implab/core/Cancellation';
6
7 class SimpleActivatable extends ActivatableMixin(AsyncComponent) {
8
9 }
10
11 class MockActivationController implements IActivationController {
12
13 _active: IActivatable = null;
14
15
16 getActive() : IActivatable {
17 return this._active;
18 }
19
20 async deactivate() {
21 if (this._active)
22 await this._active.deactivate();
23 this._active = null;
24 }
25
26 async activate(component: IActivatable) {
27 if (!component || component.isActive())
28 return;
29 component.setActivationController(this);
30
31 await component.activate();
32 }
33
34 async activating(component: IActivatable, ct: ICancellation = Cancellation.none) {
35 if (component != this._active)
36 await this.deactivate();
37 }
38
39 async activated(component: IActivatable, ct: ICancellation = Cancellation.none) {
40 this._active = component;
41 }
42
43 async deactivating(component: IActivatable, ct: ICancellation = Cancellation.none) {
44
45 }
46
47 async deactivated(component: IActivatable, ct: ICancellation = Cancellation.none) {
48 if (this._active == component)
49 this._active = null;
50 }
51 }
52
53 tape('simple activation',async function(t){
54
55 let a = new SimpleActivatable();
56 t.false(a.isActive());
57
58 await a.activate();
59 t.true(a.isActive());
60
61 await a.deactivate();
62 t.false(a.isActive());
63
64 t.end();
65 });
66
67 tape('controller activation', async function(t) {
68
69 let a = new SimpleActivatable();
70 let c = new MockActivationController();
71
72 t.false(a.isActive(), "the component is not active by default");
73 t.assert(c.getActive() == null, "the activation controller doesn't have an active component by default");
74 t.assert(a.getActivationController() == null, "the component doesn't have an activation controller by default");
75
76 t.comment("Active the component through the controller");
77 await c.activate(a);
78 t.true(a.isActive(), "The component should successfully activate");
79 t.equal(c.getActive(), a, "The controller should point to the activated component");
80 t.equal(a.getActivationController(), c, "The component should point to the controller");
81
82 t.comment("Deactive the component throug the controller");
83 await c.deactivate();
84
85 t.false(a.isActive(), "The component should successfully deactivate");
86 t.equal(c.getActive(), null, "The controller shouldn't point to any component");
87 t.equal(a.getActivationController(), c, "The componet should point to it's controller");
88
89 t.end();
90 });
91
92 tape('handle error in onActivating', async function(t) {
93 let a = new SimpleActivatable();
94
95 a.onActivating = async function() {
96 throw "Should fail";
97 };
98
99 try {
100 await a.activate();
101 t.fail("activation should fail");
102 } catch {
103 }
104
105 t.false(a.isActive(), "the component should remain inactive");
106
107 t.end();
108 }); No newline at end of file
@@ -0,0 +1,97
1 import * as tape from 'tape';
2 import { Cancellation } from '@implab/core/Cancellation';
3 import { ICancellation } from '@implab/core/interfaces';
4 import { delay } from './TestTraits';
5
6 tape('standalone cancellation', async t => {
7
8 let doCancel: (e) => void;
9
10 let ct = new Cancellation(cancel => {
11 doCancel = cancel;
12 });
13
14 let counter = 0;
15 let reason = "BILL";
16
17 t.true(ct.isSupported(), "Cancellation must be supported");
18 t.false(ct.isRequested(), "Cancellation shouldn't be requested");
19 ct.throwIfRequested();
20 t.pass("The exception shouldn't be thrown unless the cancellation is requested");
21
22 ct.register(() => counter++);
23 t.equals(counter, 0, "counter should be zero");
24
25 ct.register(() => counter++).destroy();
26
27 doCancel(reason);
28
29 t.true(ct.isRequested(), "Cancellation should be requested");
30 t.equals(counter, 1, "The registered callback should be triggered");
31
32 ct.register(() => counter++);
33 t.equals(counter, 2, "The callback should be triggered immediately");
34
35 let msg;
36 ct.register((e) => msg = e);
37 t.equals(msg, reason, "The cancellation reason should be passed to callback");
38
39 try {
40 msg = null;
41 ct.throwIfRequested();
42 t.fail("The exception should be thrown");
43 } catch (e) {
44 msg = e;
45 }
46 t.equals(msg, reason, "The cancellation reason should be catched");
47
48 t.end();
49 });
50
51 tape('async cancellation', async t => {
52
53 let ct = new Cancellation(cancel => {
54 cancel("STOP!");
55 });
56
57 try {
58 await delay(0, ct);
59 t.fail("Should thow the exception");
60 } catch (e) {
61 t.equals(e, "STOP!", "Should throw the cancellation reason");
62 }
63
64 t.end();
65 });
66
67 tape('cancel with external event', async t => {
68 let ct = new Cancellation((cancel) => {
69 setTimeout(x => cancel('STOP!'), 0);
70 })
71
72 try {
73 await delay(10000, ct);
74 t.fail("Should thow the exception");
75 } catch (e) {
76 t.equals(e, "STOP!", "Should throw the cancellation reason");
77 }
78
79 t.end();
80 });
81
82 tape('operation normal flow', async t => {
83
84 let htimeout;
85 let ct = new Cancellation((cancel) => {
86 htimeout = setTimeout(() => cancel("STOP!"), 1000);
87 });
88
89 try {
90 await delay(0, ct);
91 t.pass("Should pass");
92 } finally {
93 clearTimeout(htimeout);
94 }
95
96 t.end();
97 }); No newline at end of file
@@ -0,0 +1,63
1 import { IObservable, ICancellation, IDestroyable } from "../../build/dist/interfaces";
2 import * as TraceEvent from '../../build/dist/log/TraceEvent';
3 import { Cancellation } from "../../build/dist/Cancellation";
4 import * as TraceSource from "../../build/dist/log/TraceSource";
5 import * as tape from 'tape';
6 import { argumentNotNull } from "../../build/dist/safe";
7
8 export class TapeWriter implements IDestroyable {
9 readonly _tape: tape.Test
10
11 _subscriptions = new Array<IDestroyable>();
12
13 constructor(tape: tape.Test) {
14 argumentNotNull(tape, "tape");
15 this._tape = tape;
16 }
17
18 writeEvents(source: IObservable<TraceEvent>, ct: ICancellation = Cancellation.none) {
19 let subscription = source.on(this.writeEvent.bind(this));
20 if (ct.isSupported()) {
21 ct.register(subscription.destroy.bind(subscription));
22 }
23 this._subscriptions.push(subscription);
24 }
25
26 writeEvent(next: TraceEvent) {
27 if (next.level >= TraceSource.LogLevel) {
28 this._tape.comment("LOG " + next.arg);
29 } else if (next.level >= TraceSource.WarnLevel) {
30 this._tape.comment("WARN " + next.arg);
31 } else {
32 this._tape.comment("ERROR " + next.arg);
33 }
34 }
35
36 destroy() {
37 this._subscriptions.forEach(x => x.destroy());
38 }
39 }
40
41 export async function delay(timeout: number, ct: ICancellation = Cancellation.none) {
42 let un: IDestroyable;
43
44 try {
45 await new Promise((resolve, reject) => {
46 if (ct.isRequested()) {
47 un = ct.register(reject);
48 } else {
49 let ht = setTimeout(() => {
50 resolve();
51 }, timeout);
52
53 un = ct.register(e => {
54 clearTimeout(ht);
55 reject(e);
56 });
57 }
58 });
59 } finally {
60 if(un)
61 un.destroy();
62 };
63 } No newline at end of file
@@ -0,0 +1,69
1 import * as TraceSource from '@implab/core/log/TraceSource'
2 import * as tape from 'tape';
3 import { TapeWriter } from './TestTraits';
4
5 const sourceId = 'test/TraceSourceTests';
6
7 tape('trace message', t => {
8 let trace = TraceSource.get(sourceId);
9
10 trace.level = TraceSource.DebugLevel;
11
12 let h = trace.on((ev) => {
13 t.equal(ev.source, trace, "sender should be the current trace source");
14 t.equal(ev.level, TraceSource.DebugLevel, "level should be debug level");
15 t.equal(ev.arg, "Hello, World!", "The message should be a formatted message");
16
17 t.end();
18 });
19
20 trace.debug("Hello, {0}!", "World");
21
22 h.destroy();
23 });
24
25 tape('trace event', t => {
26 let trace = TraceSource.get(sourceId);
27
28 trace.level = TraceSource.DebugLevel;
29
30 let event = {
31 name: "custom event"
32 };
33
34 let h = trace.on((ev) => {
35 t.equal(ev.source, trace, "sender should be the current trace source");
36 t.equal(ev.level, TraceSource.DebugLevel, "level should be debug level");
37 t.equal(ev.arg, event, "The message should be the specified object");
38
39 t.end();
40 });
41
42 trace.traceEvent(TraceSource.DebugLevel, event);
43
44 h.destroy();
45 });
46
47 tape('tape comment writer', async t => {
48 let writer = new TapeWriter(t);
49
50 TraceSource.on(ts => {
51 writer.writeEvents(ts);
52 });
53
54 let trace = TraceSource.get(sourceId);
55 trace.level = TraceSource.DebugLevel;
56
57 trace.log("Hello, {0}!", 'World');
58 trace.log("Multi\n line");
59 trace.warn("Look at me!");
60 trace.error("DIE!");
61
62 writer.destroy();
63
64 trace.log("You shouldn't see it!");
65
66 t.comment("DONE");
67
68 t.end();
69 }); No newline at end of file
@@ -7,6 +7,7 def testDir = "$buildDir/test"
7 task clean {
7 task clean {
8 doLast {
8 doLast {
9 delete buildDir
9 delete buildDir
10 delete 'node_modules/@implab'
10 }
11 }
11 }
12 }
12
13
@@ -42,7 +43,7 task _buildTs(dependsOn: _npmInstall, ty
42 task _packageMeta(type: Copy) {
43 task _packageMeta(type: Copy) {
43 inputs.property("version", version)
44 inputs.property("version", version)
44 from('.') {
45 from('.') {
45 include 'package.json', 'readme.md', 'license', 'history.md'
46 include 'package.json', '.npmignore', 'readme.md', 'license', 'history.md'
46 }
47 }
47 into distDir
48 into distDir
48 doLast {
49 doLast {
@@ -133,7 +133,7
133 },
133 },
134 "minimist": {
134 "minimist": {
135 "version": "0.0.5",
135 "version": "0.0.5",
136 "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.5.tgz",
136 "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.5.tgz",
137 "integrity": "sha1-16oye87PUY+RBqxrjwA/o7zqhWY=",
137 "integrity": "sha1-16oye87PUY+RBqxrjwA/o7zqhWY=",
138 "dev": true
138 "dev": true
139 },
139 },
@@ -319,9 +319,9
319 }
319 }
320 },
320 },
321 "requirejs": {
321 "requirejs": {
322 "version": "2.3.5",
322 "version": "2.3.6",
323 "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.5.tgz",
323 "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz",
324 "integrity": "sha512-svnO+aNcR/an9Dpi44C7KSAy5fFGLtmPbaaCeQaklUz8BQhS64tWWIIlvEA5jrWICzlO/X9KSzSeXFnZdBu8nw==",
324 "integrity": "sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg==",
325 "dev": true
325 "dev": true
326 },
326 },
327 "resolve": {
327 "resolve": {
@@ -413,9 +413,9
413 }
413 }
414 },
414 },
415 "typescript": {
415 "typescript": {
416 "version": "2.9.2",
416 "version": "3.0.3",
417 "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz",
417 "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.0.3.tgz",
418 "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==",
418 "integrity": "sha512-kk80vLW9iGtjMnIv11qyxLqZm20UklzuR2tL0QAnDIygIUIemcZMxlMWudl9OOt76H3ntVzcTiddQ1/pAAJMYg==",
419 "dev": true
419 "dev": true
420 },
420 },
421 "wrappy": {
421 "wrappy": {
@@ -14,6 +14,10 requirejs.config({
14 {
14 {
15 name: "test",
15 name: "test",
16 location: "build/test"
16 location: "build/test"
17 },
18 {
19 name: "dojo",
20 location: "node_modules/dojo"
17 }
21 }
18 ],
22 ],
19 nodeRequire: require
23 nodeRequire: require
@@ -110,7 +110,7 define([
110 if (typeof (config) === "string") {
110 if (typeof (config) === "string") {
111 p = new Deferred();
111 p = new Deferred();
112 if (!contextRequire) {
112 if (!contextRequire) {
113 var shim = [config, new Uuid()].join(config.indexOf("/") != -1 ? "-" : "/");
113 var shim = [config, Uuid()].join(config.indexOf("/") != -1 ? "-" : "/");
114 define(shim, ["require", config], function (ctx, data) {
114 define(shim, ["require", config], function (ctx, data) {
115 p.resolve([data, {
115 p.resolve([data, {
116 contextRequire: ctx
116 contextRequire: ctx
@@ -1,74 +1,14
1 define(["../text/format"], function (format) {
1 define(["./TraceSource"], function (TraceSource) {
2 'use strict';
2 'use strict';
3
3
4 var listeners = [];
4 return {
5 var channels = {};
6
7 var Trace = function (name) {
8 this.name = name;
9 this._subscribers = [];
10 };
11
12 Trace.prototype.debug = function () {
13 if (Trace.level >= 4)
14 this.notify("debug", format.apply(null, arguments));
15 };
16
17 Trace.prototype.log = function () {
18 if (Trace.level >= 3)
19 this.notify("log", format.apply(null, arguments));
20 };
21
22 Trace.prototype.warn = function () {
23 if (Trace.level >= 2)
24 this.notify("warn", format.apply(null, arguments));
25
26 };
27
28 Trace.prototype.error = function () {
29 if (Trace.level >= 1)
30 this.notify("error", format.apply(null, arguments));
31 };
32
5
33 Trace.prototype.notify = function (name, msg) {
6 on: function (filter, cb) {
34 var me = this;
35 me._subscribers.forEach(function (cb) {
36 cb(me, name, msg);
37 });
38 };
39
40 Trace.prototype.subscribe = function (cb) {
41 this._subscribers.push(cb);
42 };
43
44 Trace.prototype.toString = function () {
45 return this.name;
46 };
47
48 Trace.createChannel = function (type, name, cb) {
49 var chId = name;
50 if (channels[chId])
51 return channels[chId];
52
53 var channel = new type(chId);
54 channels[chId] = channel;
55
56 Trace._onNewChannel(chId, channel);
57 cb(channel);
58 };
59
60 Trace._onNewChannel = function (chId, ch) {
61 listeners.forEach(function (listener) {
62 listener(chId, ch);
63 });
64 };
65
66 Trace.on = function (filter, cb) {
67 if (arguments.length == 1) {
7 if (arguments.length == 1) {
68 cb = filter;
8 cb = filter;
69 filter = undefined;
9 filter = undefined;
70 }
10 }
71 var d, test;
11 var test;
72 if (filter instanceof RegExp) {
12 if (filter instanceof RegExp) {
73 test = function (chId) {
13 test = function (chId) {
74 return filter.test(chId);
14 return filter.test(chId);
@@ -82,35 +22,29 define(["../text/format"], function (for
82 }
22 }
83
23
84 if (test) {
24 if (test) {
85 d = function(chId, ch) {
25 TraceSource.on(function (source) {
86 if(test(chId))
26 if (test(source.id))
87 ch.subscribe(cb);
27 source.on(cb);
88 };
28 });
89 } else {
29 } else {
90 d = function(chId, ch) {
30 TraceSource.on(function (source) {
91 ch.subscribe(cb);
31 source.on(cb);
92 };
32 });
93 }
33 }
94 listeners.push(d);
34 },
95
96 for(var chId in channels)
97 d(chId,channels[chId]);
98 };
99
35
100 Trace.load = function (id, require, cb) {
36 load: function (id, require, cb) {
101 if (id)
37 if (id) {
102 Trace.createChannel(Trace, id, cb);
38 cb(TraceSource.get(id));
103 else if (require.module && require.module.mid)
39 } else if (require.module && require.module.mid) {
104 Trace.createChannel(Trace, require.module.mid, cb);
40 cb(TraceSource.get(require.module.mid));
105 else
41 } else {
106 require(['module'], function (module) {
42 require(['module'], function (module) {
107 Trace.createChannel(Trace, module && module.id, cb);
43 cb(TraceSource.get(module && module.id));
108 });
44 });
109 };
45 }
110
46 },
111 Trace.dynamic = true;
112
47
113 Trace.level = 4;
48 dynamic: true,
114
49 };
115 return Trace;
116 }); No newline at end of file
50 });
@@ -18,13 +18,13 define(
18 var espaceString = function(s) {
18 var espaceString = function(s) {
19 if (!s)
19 if (!s)
20 return s;
20 return s;
21 return "'" + s.replace(/('|\\)/g, "\\$1") + "'";
21 return "'" + s.replace(/('|\\)/g, "\\$1").replace("\n","\\n") + "'";
22 };
22 };
23
23
24 var encode = function(s) {
24 var encode = function(s) {
25 if (!s)
25 if (!s)
26 return s;
26 return s;
27 return s.replace(/\\{|\\}|&|\\:/g, function(m) {
27 return s.replace(/\\{|\\}|&|\\:|\n/g, function(m) {
28 return map[m] || m;
28 return map[m] || m;
29 });
29 });
30 };
30 };
@@ -6,6 +6,8
6 // Copyright (c) 2010-2012 Robert Kieffer
6 // Copyright (c) 2010-2012 Robert Kieffer
7 // MIT License - http://opensource.org/licenses/mit-license.php
7 // MIT License - http://opensource.org/licenses/mit-license.php
8
8
9 declare var window: any;
10
9 let _window : any = 'undefined' !== typeof window ? window : null;
11 let _window : any = 'undefined' !== typeof window ? window : null;
10
12
11 // Unique ID creation requires a high quality random # generator. We
13 // Unique ID creation requires a high quality random # generator. We
@@ -1,1 +1,2
1 define(["./dummy", "./example"]); No newline at end of file
1 define(["./ActivatableTests", "./trace-test", "./TraceSourceTests", "./CancellationTests"]);
2 //define(["./CancellationTests"]); No newline at end of file
@@ -4,7 +4,10
4 "module": "amd",
4 "module": "amd",
5 "sourceMap": true,
5 "sourceMap": true,
6 "outDir" : "build/dist",
6 "outDir" : "build/dist",
7 "declaration": true
7 "declaration": true,
8 "lib": [
9 "ES2015"
10 ]
8 },
11 },
9 "include" : [
12 "include" : [
10 "src/ts/**/*.ts"
13 "src/ts/**/*.ts"
@@ -4,7 +4,10
4 "module": "amd",
4 "module": "amd",
5 "sourceMap": true,
5 "sourceMap": true,
6 "outDir" : "build/test",
6 "outDir" : "build/test",
7 "moduleResolution": "node"
7 "moduleResolution": "node",
8 "lib": [
9 "ES2015"
10 ]
8 },
11 },
9 "include" : [
12 "include" : [
10 "test/ts/**/*.ts"
13 "test/ts/**/*.ts"
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now