├── README.md ├── assets ├── intro-full-rxjs.gif ├── intro-rxjs.gif ├── rx-concept.gif └── vs-opt.gif ├── intro.html ├── js ├── vs-exec.js ├── vs-opt.js ├── vs-values.js ├── vs.js └── world-clock-pm.js ├── promise-observable.html ├── terminolgy.html ├── vs.html ├── world-clock-pm.html └── worldclock ├── bangkok.js ├── greenwich.js ├── japan.js ├── main.js └── rx.html /README.md: -------------------------------------------------------------------------------- 1 | # Introduction to Reactive Programming 2 | 3 | This is part of session in BKK.JS#19 event, 27 Jan 2024 at SCB Tech Siam Paragon 4 | 5 | ## Rx Terminology 6 | "reactive" and "propagation of change" 7 | ``` 8 | Rx = Observables + LINQ + Schedulers 9 | ``` 10 | 11 | ## Real world example 12 | 13 | This is case use World Clock as real world example 14 | 15 | ### How to 16 | 17 | Run `worldclock/rx.html` 18 | 19 | [code] 20 | 21 | 22 | ## Presentation Link 23 | 24 | 25 | canva 26 | 27 | 28 | 29 | [code]: (worldclock/rx.html) 30 | -------------------------------------------------------------------------------- /assets/intro-full-rxjs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viruskizz/bkkjs-reactive-programming/4502f3ccac71627f48ddcede6f00d8318a106d2c/assets/intro-full-rxjs.gif -------------------------------------------------------------------------------- /assets/intro-rxjs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viruskizz/bkkjs-reactive-programming/4502f3ccac71627f48ddcede6f00d8318a106d2c/assets/intro-rxjs.gif -------------------------------------------------------------------------------- /assets/rx-concept.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viruskizz/bkkjs-reactive-programming/4502f3ccac71627f48ddcede6f00d8318a106d2c/assets/rx-concept.gif -------------------------------------------------------------------------------- /assets/vs-opt.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viruskizz/bkkjs-reactive-programming/4502f3ccac71627f48ddcede6f00d8318a106d2c/assets/vs-opt.gif -------------------------------------------------------------------------------- /intro.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Introduction Reactive Programing 7 | 8 | 9 | 10 | 27 | 28 | -------------------------------------------------------------------------------- /js/vs-exec.js: -------------------------------------------------------------------------------- 1 | var promise = new Promise(resolve => { 2 | console.log('construct promise'); 3 | resolve("pm: Good Geek Club"); 4 | }); 5 | 6 | var observable$ = new rxjs.Observable(observer => { 7 | console.log('construct observable'); 8 | observer.next("rx: Good Geek Club"); 9 | observer.complete(); 10 | }); 11 | -------------------------------------------------------------------------------- /js/vs-opt.js: -------------------------------------------------------------------------------- 1 | var promise = new Promise(resolve => { 2 | resolve('pm: Good Geek Club'); 3 | resolve('ft: BKK.JS'); 4 | }) 5 | promise 6 | .then(res => res.includes('pm') ? res: null) 7 | .then(res => res + ' [opt]') 8 | .then(res => new Promise(resolve => setTimeout(() => resolve(res), 3000))) 9 | .then(res => console.log(res)); 10 | 11 | 12 | var { Observable, map, delay, filter } = rxjs; 13 | 14 | var observable$ = new Observable(observer => { 15 | observer.next("rx: Good Geek Club"); 16 | observer.next("rx: 42 Bangkok"); 17 | observer.next("ft: BKK.JS"); 18 | observer.next("rx: LSEG"); 19 | observer.complete(); 20 | }); 21 | observable$.pipe( 22 | filter(res => res.includes('rx')), 23 | map(res => res + ' [opt]'), 24 | delay(3000) 25 | ).subscribe(res => console.log(res)) -------------------------------------------------------------------------------- /js/vs-values.js: -------------------------------------------------------------------------------- 1 | var promise = new Promise(resolve => { 2 | resolve("pm: Good Geek Club"); 3 | resolve("pm: 42 Bangkok"); 4 | resolve("pm: LSEG"); 5 | }); 6 | promise.then(res => console.log(res)); 7 | 8 | var observable$ = new rxjs.Observable(observer => { 9 | observer.next("rx: Good Geek Club"); 10 | observer.next("rx: filter"); 11 | observer.next("rx: 42 Bangkok"); 12 | observer.next("rx: LSEG"); 13 | observer.complete(); 14 | }); 15 | observable$.subscribe(res => console.log(res)); -------------------------------------------------------------------------------- /js/vs.js: -------------------------------------------------------------------------------- 1 | const { Observable, map, delay, filter } = rxjs; 2 | window.onload = () => { 3 | promise.exec(); 4 | rx.exec(); 5 | promise.values(); 6 | rx.values(); 7 | promise.opt(); 8 | rx.opt(); 9 | } 10 | const promise = { 11 | exec: () => { 12 | const promise = new Promise(resolve => { 13 | console.log('construct promise'); 14 | appendList('pm-list-exec', 'construct promise'); 15 | resolve("pm: Good Geek Club"); 16 | }); 17 | }, 18 | values: () => { 19 | const promise = new Promise(resolve => { 20 | resolve("pm: Good Geek Club"); 21 | resolve("pm: 42 Bangkok"); 22 | resolve("pm: LSEG"); 23 | }); 24 | promise.then(res => { 25 | console.log(res); 26 | appendList('pm-list-values', res); 27 | }); 28 | }, 29 | opt: () => { 30 | const promise = new Promise(resolve => { 31 | resolve('pm: Good Geek Club'); 32 | resolve('ft: BKK.JS'); 33 | }) 34 | promise 35 | .then(res => res.includes('pm') ? res: null) 36 | .then(res => res + ' [opt]') 37 | .then(res => new Promise(resolve => setTimeout(() => resolve(res), 3000))) 38 | .then(res => appendList('pm-list-opt', res)); 39 | } 40 | } 41 | const rx = { 42 | exec: () => { 43 | const observable$ = new Observable(observer => { 44 | console.log('construct observable'); 45 | appendList('rx-list-exec', 'construct observable'); 46 | observer.next("rx: Good Geek Club"); 47 | observer.complete(); 48 | }); 49 | }, 50 | values: () => { 51 | const observable$ = new Observable(observer => { 52 | observer.next("rx: Good Geek Club"); 53 | observer.next("rx: filter"); 54 | observer.next("rx: 42 Bangkok"); 55 | observer.next("rx: LSEG"); 56 | observer.complete(); 57 | }); 58 | observable$.subscribe((res) => { 59 | console.log(res); 60 | appendList('rx-list-values', res); 61 | }); 62 | }, 63 | opt: () => { 64 | const observable$ = new Observable(observer => { 65 | observer.next("rx: Good Geek Club"); 66 | observer.next("rx: 42 Bangkok"); 67 | observer.next("ft: BKK.JS"); 68 | observer.next("rx: LSEG"); 69 | observer.complete(); 70 | }); 71 | observable$.pipe( 72 | filter(res => res.includes('rx')), 73 | map(res => res + ' [opt]'), 74 | delay(3000) 75 | ).subscribe(res => appendList('rx-list-opt', res)) 76 | } 77 | } 78 | function appendList(divId, data) { 79 | const div = document.getElementById(divId); 80 | const list = document.createElement('li'); 81 | list.innerHTML = data; 82 | div.appendChild(list); 83 | } -------------------------------------------------------------------------------- /js/world-clock-pm.js: -------------------------------------------------------------------------------- 1 | const { interval, map, scan } = rxjs; 2 | const timezones = [ 3 | {id: 'greenwich', diff: 0}, 4 | {id: 'bangkok', diff: 7 * 60 * 60}, 5 | {id: 'japan', diff: 9 * 60 * 60}, 6 | ] 7 | let unixTimestamp = Math.floor(new Date().getTime() / 1000); 8 | 9 | // interaval function do something 10 | setInterval(() => { 11 | ++unixTimestamp; 12 | for (const tz of timezones) { 13 | const el = document.getElementById(tz.id) 14 | el.innerHTML = datetimeText(time + tz.diff); 15 | } 16 | }, 1000); 17 | 18 | function datetimeText(time) { 19 | const s = time % 60 20 | const m = Math.floor((time / 60) % 60); 21 | const h = Math.floor(time / (60 * 60) % 24); 22 | const f0 = (t) => (t < 9 ) ? `0${t}`: t 23 | return `${f0(h)}:${f0(m)}:${f0(s)}`; 24 | } 25 | -------------------------------------------------------------------------------- /promise-observable.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Promise vs Observable 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Promise vs Observable

14 | 15 |
16 |

Execution

17 |
18 |
19 |

Promise

20 |
21 |
22 |
23 |

Observable

24 |
25 |
26 |
27 |
28 | 29 |
30 |

Single vs Multiple values

31 |
32 |
33 |

Promise

34 |
35 |
36 |
37 |

Observable

38 |
39 |
40 |
41 |
42 | 43 |
44 |

Operators

45 |
46 |
47 |

Promise

48 |
49 |
50 |
51 |

Observable

52 |
53 |
54 |
55 |
56 |
57 | 58 | -------------------------------------------------------------------------------- /terminolgy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Introduction Reactive Programing 7 | 8 | 9 | 10 | 18 | 19 | -------------------------------------------------------------------------------- /vs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Promise vs Observable on console 7 | 8 | 9 | 10 | 11 | 12 | 13 |

Debug console only

14 | 15 | -------------------------------------------------------------------------------- /world-clock-pm.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | World Clock Rx 7 | 8 | 9 | 10 | 11 | 12 |
13 |

World Clock Application

14 |
15 |
16 |

Japan

17 |

GMT+9 18 |

19 | 00:00:00 20 |

21 |
22 |
23 |

Greenwich

24 |

GMT 25 |

26 | 00:00:00 27 |

28 |
29 |
30 |

Bangkok

31 |

GMT+7 32 |

33 | 00:00:00 34 |

35 |
36 |
37 | 38 |
39 | 40 | -------------------------------------------------------------------------------- /worldclock/bangkok.js: -------------------------------------------------------------------------------- 1 | import { gmtClock$, timestampText, HR_SECOND } from './main.js' 2 | const { map } = rxjs; 3 | 4 | gmtClock$.pipe( 5 | map(t => t + 7 * HR_SECOND) 6 | ).subscribe(time => { 7 | const el = document.getElementById('bangkok') 8 | el.innerHTML = timestampText(time); 9 | }) 10 | -------------------------------------------------------------------------------- /worldclock/greenwich.js: -------------------------------------------------------------------------------- 1 | import { gmtClock$, timestampText, HR_SECOND } from './main.js' 2 | const { map } = rxjs; 3 | 4 | gmtClock$.subscribe(time => { 5 | const el = document.getElementById('greenwich') 6 | el.innerHTML = timestampText(time); 7 | }) 8 | -------------------------------------------------------------------------------- /worldclock/japan.js: -------------------------------------------------------------------------------- 1 | import { gmtClock$, timestampText, HR_SECOND } from './main.js' 2 | const { map } = rxjs; 3 | 4 | gmtClock$.pipe( 5 | map(t => t + 9 * HR_SECOND) 6 | ).subscribe(time => { 7 | const el = document.getElementById('japan') 8 | el.innerHTML = timestampText(time); 9 | }) 10 | -------------------------------------------------------------------------------- /worldclock/main.js: -------------------------------------------------------------------------------- 1 | const { interval, scan } = rxjs; 2 | 3 | /* 4 | * Emit current unixtimestamp every second 5 | * Create current unixtimestamp as initial value 6 | */ 7 | const gmtClock$ = interval(1000).pipe( 8 | scan(ts => ++ts, Math.floor(new Date().getTime() / 1000)) 9 | ); 10 | 11 | const HR_SECOND = 60 * 60; 12 | 13 | const timestampText = (time) => { 14 | const s = time % 60 15 | const m = Math.floor((time / 60) % 60); 16 | const h = Math.floor(time / (60 * 60) % 24); 17 | const f0 = (t) => (t < 9 ) ? `0${t}`: t 18 | return `${f0(h)}:${f0(m)}:${f0(s)}`; 19 | } 20 | 21 | export { gmtClock$, HR_SECOND, timestampText } 22 | 23 | /** 24 | * 1 data stream split to 3 data stream 25 | */ 26 | // gmtTime$.pipe( 27 | // switchMap(time => from(timezones).pipe(map(tz => ({...tz, time})))) 28 | // ).subscribe(tz => { 29 | // const el = document.getElementById(tz.id) 30 | // el.innerHTML = datetimeText(tz.time + tz.diff); 31 | // }); -------------------------------------------------------------------------------- /worldclock/rx.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | World Clock Rx 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |

World Clock Application

18 |
19 |
20 |

Japan

21 |

GMT+9 22 |

23 | 00:00:00 24 |

25 |
26 |
27 |

Greenwich

28 |

GMT 29 |

30 | 00:00:00 31 |

32 |
33 |
34 |

Bangkok

35 |

GMT+7 36 |

37 | 00:00:00 38 |

39 |
40 |
41 | 42 |
43 | 44 | --------------------------------------------------------------------------------