observable.md
181 lines
| 8.0 KiB
| text/x-minidsrc
|
MarkdownLexer
|
|
r25 | # Observable | |
| Универсальный способ организации потока сообщений. Данный механизм может | |||
| использоваться для оповещения об изменениях состояний объектов или для доставки | |||
| самостоятельных событий, например, связанных с действиями пользователя. | |||
| Является реализацией классического шаблона наблюдателя с возможность сообщить | |||
| о коце потока событий. Данная реализация не содержит никаких дополнительных | |||
| функций, таких как фильтрация, канал с состоянием, преобразования сообщений и | |||
| т.п. Это сделано специально, чтобы реализация оставалась максимально простой. | |||
| Пример того, как можно создать последовательность из 10 событий: | |||
| ```ts | |||
| var events = new Observable(async (notify, error, complete) => { | |||
| // цикл в котором возникает событие | |||
| for(let i = 0; i < 10; i++) { | |||
| await delay(1000); | |||
| // в качестве данных передается номер события | |||
| notify(i); | |||
| } | |||
| // по окончании последовательности информируем, что событий больше не будет | |||
| compelte(); | |||
| }); | |||
| // создаем окно с отображением хода событий | |||
| var progress = showProgress({ min: 0, max: 9, current: 0}); | |||
| // подписываемся на события | |||
| events.on( | |||
| // обработчик очередного события | |||
| msg => { | |||
| progress.setValue(msg); | |||
|
|
r26 | }. | |
|
|
r25 | // обработчик ошибки | |
| e => { | |||
| progress.showError(e); | |||
| }, | |||
| // обработчик конца потока | |||
| () => { | |||
| progress.close(); | |||
| } | |||
| ); | |||
|
|
r26 | ||
| // ожидание следующего события | |||
| let firstEvent = await events.next(); | |||
|
|
r25 | ``` | |
|
|
r26 | `Observable` можно создавать из событий другого объекта, например, виджета: | |
|
|
r25 | ||
| ```ts | |||
|
|
r26 | // клсс | |
| 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]) ); | |||
| }); | |||
| } | |||
|
|
r25 | } | |
| ``` | |||
|
|
r26 | Если объект инкапсулирует в себе `Observable`, он также может сохранить методы | |
| для оповещения подписчиков для дальнейшего их использования внутри класса. | |||
|
|
r25 | ||
| ```ts | |||
|
|
r26 | // класс, который будет генерировать события местоположения | |
| class PositionTracker implements IDestroyable { | |||
| // _nextPosition и _complete будут связаны с position при создании | |||
| // экземпляра PositionTracker. | |||
|
|
r25 | _nextPosition: (pos: Position) => void | |
| _complete: () => void | |||
|
|
r26 | readonly position: IObservable<Position> | |
|
|
r25 | ||
|
|
r26 | // конструктор | |
| constructor(...args: any[]) { | |||
|
|
r25 | super(args); | |
|
|
r26 | // создаем Observable | |
|
|
r25 | this.position = new Observable<Position>((notify, error, complete) => { | |
|
|
r26 | // сохраняем методы для оповещения о новом местоположении | |
|
|
r25 | this._nextPosition = notify; | |
|
|
r26 | // метод об оповещении конца потока событий | |
|
|
r25 | this._complete = complete | |
| }); | |||
| } | |||
|
|
r26 | // метод для очистки ресурсов | |
|
|
r25 | destroy() { | |
| this._complete(); | |||
| super(); | |||
| } | |||
| } | |||
|
|
r26 | ``` | |
|
|
r25 | ||
|
|
r26 | Существует также несколько варинатов получения сообщений | |
| ```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); | |||
|
|
r59 | ||
|
|
r26 | // преобразуем позицию на экране в координаты карты | |
| 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); | |||
| }); | |||
|
|
r25 | ``` |
