# HG changeset patch # User cin # Date 2018-09-21 13:32:52 # Node ID 0b0a30e050baab6474d91c6067402ec2541f5eac # Parent 35af87458b03c6960778d0973f834d26c5958210 the documentation on observables is added more tests diff --git a/docs/en/observable.md b/docs/en/observable.md --- a/docs/en/observable.md +++ b/docs/en/observable.md @@ -31,7 +31,7 @@ events.on( // обработчик очередного события msg => { progress.setValue(msg); - }, + }. // обработчик ошибки e => { progress.showError(e); @@ -41,40 +41,55 @@ events.on( progress.close(); } ); + +// ожидание следующего события +let firstEvent = await events.next(); ``` -Пример создания `Observable` из событий другого объекта, например, виджета: +`Observable` можно создавать из событий другого объекта, например, виджета: ```ts -postCreate() { - // превращаем события виджета в Observable - this.mouseMove = new Observable((notify) => { - this.moveArea.on('mousemove',(x) => notify(x.) ); - }); +// клсс +class Canvas { + readonly mouseMove: IObservable<[number,number]> + + postCreate() { + // превращаем события виджета в Observable + this.mouseMove = new Observable<[number,number]>((notify) => { + this.mousePad.on('mousemove',(e) => notify([e.clientX, e.clientY]) ); + }); + } } ``` -Пример инициализации `Observable` внутри класса и генерация событий: +Если объект инкапсулирует в себе `Observable`, он также может сохранить методы +для оповещения подписчиков для дальнейшего их использования внутри класса. ```ts - -class PositionWidget extends Widget { +// класс, который будет генерировать события местоположения +class PositionTracker implements IDestroyable { + // _nextPosition и _complete будут связаны с position при создании + // экземпляра PositionTracker. _nextPosition: (pos: Position) => void - _complete: () => void - readonly position: Observable; + readonly position: IObservable - constructor(...args[]) { + // конструктор + constructor(...args: any[]) { super(args); + // создаем Observable this.position = new Observable((notify, error, complete) => { + // сохраняем методы для оповещения о новом местоположении this._nextPosition = notify; + // метод об оповещении конца потока событий this._complete = complete }); } + // метод для очистки ресурсов destroy() { this._complete(); @@ -82,4 +97,39 @@ class PositionWidget extends Widget { } } +``` + +## Observable и последовательности + +Можно сичтать, что `Observable` это некоторая аналогия итератора только в +парадигме событийного (или реактивного) программировния. Следует также понимать, +что при переходе от синхронного процедурного программирования к событийному так +же меняется и направление управления (Inverse Of Control), что означает +следующее: + +* при работе с итераторами клиенты сами определяют момент чтения следующего + элемента последовательности. +* при работе с `Observable` клиенты вынуждены обрабатывать эти события по мере + их поступления и не могут на это повлиять. + +Последний пункт можно изменить применив, например, буффер или канал с +состоянием, т.е. очередь, но данные механизмы выходят за рамки простого шаблона +наблюдателя. + +```ts +while(1) { + // ожидаем следующее событие, по сути это подписка только на одно событие + let next = await events.next(); + + // такой цикл может пропускать сообщения, поскольку асинхронная операция + // позволит возобновить создание новых событий, на которые мы не подписаны + await processEvent(next); + + // не только асинхронные операции могут привести к пропуску события + // например вызов метода, который приводит к созданию события так же + // приведет к тому, что созданное событие не будет обработано в текущем + // цикле + doSmthAndRiseEvent(); +} + ``` \ No newline at end of file diff --git a/docs/ru/observable.md b/docs/ru/observable.md --- a/docs/ru/observable.md +++ b/docs/ru/observable.md @@ -31,7 +31,7 @@ events.on( // обработчик очередного события msg => { progress.setValue(msg); - }, + }. // обработчик ошибки e => { progress.showError(e); @@ -41,45 +41,146 @@ events.on( progress.close(); } ); + +// ожидание следующего события +let firstEvent = await events.next(); ``` -Пример создания `Observable` из событий другого объекта, например, виджета: +`Observable` можно создавать из событий другого объекта, например, виджета: ```ts -postCreate() { - // превращаем события виджета в Observable - this.mouseMove = new Observable((notify) => { - this.moveArea.on('mousemove',(x) => notify(x.) ); - }); +// клсс +class Canvas { + readonly mouseMove: IObservable<[number,number]> + + postCreate() { + // превращаем события виджета в Observable + this.mouseMove = new Observable<[number,number]>((notify) => { + this.mousePad.on('mousemove',(e) => notify([e.clientX, e.clientY]) ); + }); + } } ``` -Пример инициализации `Observable` внутри класса и генерация событий: +Если объект инкапсулирует в себе `Observable`, он также может сохранить методы +для оповещения подписчиков для дальнейшего их использования внутри класса. ```ts - -class PositionWidget extends Widget { +// класс, который будет генерировать события местоположения +class PositionTracker implements IDestroyable { + // _nextPosition и _complete будут связаны с position при создании + // экземпляра PositionTracker. _nextPosition: (pos: Position) => void - _complete: () => void - readonly position: Observable; + readonly position: IObservable - constructor(...args[]) { + // конструктор + constructor(...args: any[]) { super(args); + // создаем Observable this.position = new Observable((notify, error, complete) => { + // сохраняем методы для оповещения о новом местоположении this._nextPosition = notify; + // метод об оповещении конца потока событий this._complete = complete }); } + // метод для очистки ресурсов destroy() { this._complete(); super(); } } +``` +Существует также несколько варинатов получения сообщений + +```ts +// регистрация метода для получений событий +let subscription = pushEvents.on((msg) => { + displayPopup(msg); +}); + +// подписку можно отменить, после чего обработчики больше не будут вызываться +subcription.destroy(); + +// если требуется получить только одно сообщение можно использовать +// асинхронный метод next(ct?: ICancellation) + +let msg = await pushEvents.next(); + +// пример метода для получения координат с карты, который использует +// событие нажатия мышью для определения координат. + +class Map { + /** + + Получает координаты по щелчку мыши. + + @async + + @returns [lon,lat] + + */ + async peekCoordinates(ct: ICancellation = Cancellation.none) { + // получаем событие клика + let evt = this.viewport.click.next(ct); + + // преобразуем позицию на экране в координаты карты + return this.clientToCoodinates([evt.clientx,evt.clientY]); + } +} + + +let map : Map; // где-то объявлено + +// пример получения координат с карты +let coords = await map.peekCoordinates(); + +``` + +## Observable и последовательности + +Можно сичтать, что `Observable` это некоторая аналогия итератора только в +парадигме событийного (или реактивного) программировния. Следует также понимать, +что при переходе от синхронного процедурного программирования к событийному так +же меняется и направление управления (Inverse Of Control), что означает +следующее: + +* при работе с итераторами клиенты сами определяют момент чтения следующего + элемента последовательности. +* при работе с `Observable` клиенты вынуждены обрабатывать эти события по мере + их поступления и не могут на это повлиять. + +Последний пункт можно изменить применив, например, буффер или канал с +состоянием, т.е. очередь, но данные механизмы выходят за рамки простого шаблона +наблюдателя. + +```ts +// обработка в цикле не гарантирует получения всех сообщений +while(1) { + // ожидаем следующее событие, по сути это подписка только на одно событие + let next = await events.next(); + + // такой цикл может пропускать сообщения, поскольку асинхронная операция + // позволит возобновить создание новых событий, на которые мы не подписаны + await processEvent(next); + + // не только асинхронные операции могут привести к пропуску события + // например вызов метода, который приводит к созданию события так же + // приведет к тому, что созданное событие не будет обработано в текущем + // цикле + doSmthAndRiseEvent(); +} + +// для получения всех сообщений нужно регистрировать подписчика +events.on((data) => { + // будет вызван для всех сообщений + processEvent(data); +}); ``` \ No newline at end of file diff --git a/src/ts/Observable.ts b/src/ts/Observable.ts --- a/src/ts/Observable.ts +++ b/src/ts/Observable.ts @@ -176,6 +176,7 @@ export class Observable implements IO this._notify(guard); this._observers = []; + this._complete = true; } protected _notifyCompleted() { @@ -189,5 +190,6 @@ export class Observable implements IO this._notify(guard); this._observers = []; + this._complete = true; } } \ No newline at end of file diff --git a/test/ts/ObservableTests.ts b/test/ts/ObservableTests.ts --- a/test/ts/ObservableTests.ts +++ b/test/ts/ObservableTests.ts @@ -17,20 +17,58 @@ tape('events sequence example', async t await delay(0); notify(i); } + complete(); resolve(); }); }); let count = 0; - events.on(x => count = count + x); + let complete = false; + events.on(x => count = count + x, null, () => complete = true); let first = await events.next(); t.equals(first, 0, "the first event"); + t.false(complete, "the sequence is not complete"); await done; t.equals(count, 45, "the summ of the evetns"); + t.true(complete, "the sequence is complete"); + + t.end(); +}); + +tape('event sequence termination', async t => { + let events: IObservable + + let done = new Promise((resolve) => { + events = new Observable(async (notify, fail, complete) => { + await delay(0); + notify(1); + complete(); + notify(2); + complete(); + fail("Sequence terminated"); + resolve(); + }); + }); + + let count = 0; + events.on(() => {}, (e) => count++, () => count++); + + let first = await events.next(); + t.equals(first, 1, "the first message"); + try { + await events.next(); + t.fail("shoud throw an exception"); + } catch(e) { + t.pass("the sequence is terminated"); + } + + await done; + + t.equals(count, 1, "the sequence must be terminated once"); t.end(); }); \ No newline at end of file