| @@ -0,0 +1,36 | |||
|
|
1 | import { TraceSource, DebugLevel } from '@implab/core/log/TraceSource' | |
|
|
2 | import * as tape from 'tape'; | |
|
|
3 | import { TapeWriter, delay } from './TestTraits'; | |
|
|
4 | import { Observable } from '@implab/core/Observable'; | |
|
|
5 | import { IObservable } from '@implab/core/interfaces'; | |
|
|
6 | ||
|
|
7 | let trace = TraceSource.get("ObservableTests"); | |
|
|
8 | ||
|
|
9 | tape('events sequence example', async t => { | |
|
|
10 | ||
|
|
11 | ||
|
|
12 | let events: IObservable<number> | |
|
|
13 | ||
|
|
14 | let done = new Promise<void>((resolve) => { | |
|
|
15 | events = new Observable<number>(async (notify, fail, complete) => { | |
|
|
16 | for (let i = 0; i < 10; i++) { | |
|
|
17 | await delay(0); | |
|
|
18 | notify(i); | |
|
|
19 | } | |
|
|
20 | resolve(); | |
|
|
21 | }); | |
|
|
22 | }); | |
|
|
23 | ||
|
|
24 | let count = 0; | |
|
|
25 | events.on(x => count = count + x); | |
|
|
26 | ||
|
|
27 | let first = await events.next(); | |
|
|
28 | ||
|
|
29 | t.equals(first, 0, "the first event"); | |
|
|
30 | ||
|
|
31 | await done; | |
|
|
32 | ||
|
|
33 | t.equals(count, 45, "the summ of the evetns"); | |
|
|
34 | ||
|
|
35 | t.end(); | |
|
|
36 | }); No newline at end of file | |
| @@ -1,135 +1,85 | |||
|
|
1 | 1 | # Observable |
|
|
2 | 2 | |
|
|
3 | 3 | Универсальный способ организации потока сообщений. Данный механизм может |
|
|
4 | 4 | использоваться для оповещения об изменениях состояний объектов или для доставки |
|
|
5 | 5 | самостоятельных событий, например, связанных с действиями пользователя. |
|
|
6 | 6 | |
|
|
7 | 7 | Является реализацией классического шаблона наблюдателя с возможность сообщить |
|
|
8 | 8 | о коце потока событий. Данная реализация не содержит никаких дополнительных |
|
|
9 | 9 | функций, таких как фильтрация, канал с состоянием, преобразования сообщений и |
|
|
10 | 10 | т.п. Это сделано специально, чтобы реализация оставалась максимально простой. |
|
|
11 | 11 | |
|
|
12 | 12 | Пример того, как можно создать последовательность из 10 событий: |
|
|
13 | 13 | |
|
|
14 | 14 | ```ts |
|
|
15 | 15 | var events = new Observable(async (notify, error, complete) => { |
|
|
16 | 16 | // цикл в котором возникает событие |
|
|
17 | 17 | for(let i = 0; i < 10; i++) { |
|
|
18 | 18 | await delay(1000); |
|
|
19 | 19 | // в качестве данных передается номер события |
|
|
20 | 20 | notify(i); |
|
|
21 | 21 | } |
|
|
22 | 22 | // по окончании последовательности информируем, что событий больше не будет |
|
|
23 | 23 | compelte(); |
|
|
24 | 24 | }); |
|
|
25 | 25 | |
|
|
26 | 26 | // создаем окно с отображением хода событий |
|
|
27 | 27 | var progress = showProgress({ min: 0, max: 9, current: 0}); |
|
|
28 | 28 | |
|
|
29 | 29 | // подписываемся на события |
|
|
30 | 30 | events.on( |
|
|
31 | 31 | // обработчик очередного события |
|
|
32 | 32 | msg => { |
|
|
33 | 33 | progress.setValue(msg); |
|
|
34 | 34 | }. |
|
|
35 | 35 | // обработчик ошибки |
|
|
36 | 36 | e => { |
|
|
37 | 37 | progress.showError(e); |
|
|
38 | 38 | }, |
|
|
39 | 39 | // обработчик конца потока |
|
|
40 | 40 | () => { |
|
|
41 | 41 | progress.close(); |
|
|
42 | 42 | } |
|
|
43 | 43 | ); |
|
|
44 | ||
|
|
45 | // ожидание следующего события | |
|
|
46 | let firstEvent = await events.next(); | |
|
|
47 | 44 | ``` |
|
|
48 | 45 | |
|
|
49 |
`Observable` |
|
|
|
46 | Пример создания `Observable` из событий другого объекта, например, виджета: | |
|
|
50 | 47 | |
|
|
51 | 48 | ```ts |
|
|
52 | // клсс | |
|
|
53 | class Canvas { | |
|
|
54 | readonly mouseMove: IObservable<[number,number]> | |
|
|
55 | ||
|
|
56 | postCreate() { | |
|
|
57 | // превращаем события виджета в Observable | |
|
|
58 | this.mouseMove = new Observable<[number,number]>((notify) => { | |
|
|
59 | this.mousePad.on('mousemove',(e) => notify([e.clientX, e.clientY]) ); | |
|
|
60 | }); | |
|
|
61 | } | |
|
|
49 | postCreate() { | |
|
|
50 | // превращаем события виджета в Observable | |
|
|
51 | this.mouseMove = new Observable((notify) => { | |
|
|
52 | this.moveArea.on('mousemove',(x) => notify(x.) ); | |
|
|
53 | }); | |
|
|
62 | 54 | } |
|
|
63 | 55 | |
|
|
64 | 56 | ``` |
|
|
65 | 57 | |
|
|
66 | Если объект инкапсулирует в себе `Observable`, он также может сохранить методы | |
|
|
67 | для оповещения подписчиков для дальнейшего их использования внутри класса. | |
|
|
58 | Пример инициализации `Observable` внутри класса и генерация событий: | |
|
|
68 | 59 | |
|
|
69 | 60 | ```ts |
|
|
70 | // класс, который будет генерировать события местоположения | |
|
|
71 | class PositionTracker implements IDestroyable { | |
|
|
72 | // _nextPosition и _complete будут связаны с position при создании | |
|
|
73 | // экземпляра PositionTracker. | |
|
|
61 | ||
|
|
62 | class PositionWidget extends Widget { | |
|
|
74 | 63 |
|
|
|
64 | ||
|
|
75 | 65 | _complete: () => void |
|
|
76 | 66 | |
|
|
77 |
readonly position: |
|
|
|
67 | readonly position: Observable<Position>; | |
|
|
78 | 68 | |
|
|
79 | // конструктор | |
|
|
80 | constructor(...args: any[]) { | |
|
|
69 | constructor(...args[]) { | |
|
|
81 | 70 | super(args); |
|
|
82 | 71 | |
|
|
83 | // создаем Observable | |
|
|
84 | 72 |
|
|
|
85 | // сохраняем методы для оповещения о новом местоположении | |
|
|
86 | 73 |
|
|
|
87 | // метод об оповещении конца потока событий | |
|
|
88 | 74 |
|
|
|
89 | 75 | }); |
|
|
90 | 76 | } |
|
|
91 | 77 | |
|
|
92 | // метод для очистки ресурсов | |
|
|
93 | 78 |
|
|
|
94 | 79 | this._complete(); |
|
|
95 | 80 | |
|
|
96 | 81 | super(); |
|
|
97 | 82 | } |
|
|
98 | 83 | } |
|
|
99 | 84 | |
|
|
100 | ``` | |
|
|
101 | ||
|
|
102 | ## Observable и последовательности | |
|
|
103 | ||
|
|
104 | Можно сичтать, что `Observable` это некоторая аналогия итератора только в | |
|
|
105 | парадигме событийного (или реактивного) программировния. Следует также понимать, | |
|
|
106 | что при переходе от синхронного процедурного программирования к событийному так | |
|
|
107 | же меняется и направление управления (Inverse Of Control), что означает | |
|
|
108 | следующее: | |
|
|
109 | ||
|
|
110 | * при работе с итераторами клиенты сами определяют момент чтения следующего | |
|
|
111 | элемента последовательности. | |
|
|
112 | * при работе с `Observable` клиенты вынуждены обрабатывать эти события по мере | |
|
|
113 | их поступления и не могут на это повлиять. | |
|
|
114 | ||
|
|
115 | Последний пункт можно изменить применив, например, буффер или канал с | |
|
|
116 | состоянием, т.е. очередь, но данные механизмы выходят за рамки простого шаблона | |
|
|
117 | наблюдателя. | |
|
|
118 | ||
|
|
119 | ```ts | |
|
|
120 | while(1) { | |
|
|
121 | // ожидаем следующее событие, по сути это подписка только на одно событие | |
|
|
122 | let next = await events.next(); | |
|
|
123 | ||
|
|
124 | // такой цикл может пропускать сообщения, поскольку асинхронная операция | |
|
|
125 | // позволит возобновить создание новых событий, на которые мы не подписаны | |
|
|
126 | await processEvent(next); | |
|
|
127 | ||
|
|
128 | // не только асинхронные операции могут привести к пропуску события | |
|
|
129 | // например вызов метода, который приводит к созданию события так же | |
|
|
130 | // приведет к тому, что созданное событие не будет обработано в текущем | |
|
|
131 | // цикле | |
|
|
132 | doSmthAndRiseEvent(); | |
|
|
133 | } | |
|
|
134 | ||
|
|
135 | 85 | ``` No newline at end of file |
| @@ -1,2 +1,3 | |||
|
|
1 |
|
|
|
|
2 | //define(["./CancellationTests"]); No newline at end of file | |
|
|
1 | //define(["./ActivatableTests", "./trace-test", "./TraceSourceTests", "./CancellationTests"]); | |
|
|
2 | //define(["./CancellationTests"]); | |
|
|
3 | define(["./ObservableTests"]); No newline at end of file | |
General Comments 0
You need to be logged in to leave comments.
Login now
