# ReactiveJS
## Functional Reactive Programming (FRP)
### 函數式程式
* 聲明式
* 純函數
* 資料不可變
#### 命令式、聲明式
```javascript=
命令式
function double(arr) {
const results = []
for (let i = 0; i < arr.length; i++) {
results.push(arr[i] * 2)
}
return results
}
function addOne(arr) {
const results = []
for (let i = 0; i < arr.length; i++) {
results.push(arr[i] + 1) }
return results
}
```
```javascript=
聲明式
function double(arr) {
return arr.map(function(item) {return item * 2});
}
function addOne(arr) {
return arr.map(function(item) {return item + 1});
}
```
```javascript=
const double = arr => arr.map(item => item * 2);
const addOne = arr => arr.map(item => item + 1);
```
### 響應式程式
EXCEL

### Reactive Extension

## 拉(PULL)、推(PUSH)
PULL和PUSH描述了數據生產者如何與數據使用者溝通。
PULL -> 數據的使用者決定了何時需要取得資料。
PUSH -> 數據的生產者決定了何時需要傳送資料。
## Design pattern
Observer Pattern

https://www.tutorialspoint.com/design_pattern/observer_pattern.htm
Iterator Pattern

https://www.tutorialspoint.com/design_pattern/iterator_pattern.htm
## 核心概念
1. Observable - 可觀察的物件
2. Observer - 觀察者物件
3. Subscription - 訂閱物件
4. Operators - 運算子
5. Subject - 廣播資料傳個多個 Observer
6. Schedulers - 控制事件併發狀況
## Observer
觀察者物件
```javascript=
observable$.subscribe({
next: () => {},
error: () => {},
complete: () => {},
})
```
## Subscription
訂閱物件
```javascript=
const subscription = observable$.subscribe({
next: () => {},
error: () => {},
complete: () => {},
})
subscription.unsubscribe();
```
## Operators
運算子
```javascript=
const subscription = observable$.pipe(
map((e) => return e + 'test')
)
.subscribe({
next: () => {},
error: () => {},
complete: () => {},
})
```
## Subject
廣播資料傳個多個 Observer
```javascript=
import { Subject, from } from 'rxjs';
const subject = new Subject<number>();
subject.subscribe({
next: (v) => console.log(`observerA: ${v}`)
});
subject.subscribe({
next: (v) => console.log(`observerB: ${v}`)
});
const observable = from([1, 2, 3]);
observable.subscribe(subject); // You can subscribe providing a Subject
// Logs:
// observerA: 1
// observerB: 1
// observerA: 2
// observerB: 2
// observerA: 3
// observerB: 3
```
## Observable
### 基本Observable
```javascript=
const observable$ = new Observable(observer => {
observer.next(1);
observer.next(2);
observer.next(3);
observer.error('error occur');
observer.complete();
});
const sub = observable$.subscribe({
next: data => console.log(data),
complete : () => console.log('complete'),
error: (error) => console.log(error)
});
```
* 練習1. 實現顯示持續計數增加的Observable,並在十秒後完成。
```javascript=
const observable$ = new Observable(observer => {
})
const sub = observable$.subscribe({
next: data => console.log(data),
complete : () => console.log('complete'),
error: (error) => console.log(error)
})
/*
1
2
3
4
5
6
7
8
9
10
complete
*/
```
### 基本Observable - unsubscribe()
```javascript=
const observable$ = new Observable(observer => {
observer.next(1);
observer.next(2);
observer.next(3);
return () => {
console.log('unsubscribe');
}
});
const sub = observable$.subscribe({
next: data => console.log(data),
complete : () => console.log('complete'),
error: (error) => console.log(error)
})
sub.unsubscribe();
```
* 練習2. 實現顯示持續計數增加的Observable,並在五秒後停止訂閱。
```javascript=
const observable$ = new Observable(observer => {
});
const sub = observable$.subscribe({
next: data => console.log(data),
complete : () => console.log('complete'),
error: (error) => console.log(error)
})
setTimeout(() => sub.unsubscribe(), 5000);
```
### 遍歷 Observable
```javascript=
const arr = [1,2,3,4];
function getArrObservable(arr) {
return new Observable(observer => {
arr.forEach(function(element) {
observer.next(element);
});
observer.complete();
});
})
getArrObservable(arr).subscribe({
next: data => console.log(data),
error: error => console.log(error),
complete: () => console.log('complete')
})
```
### Promise Observable
```javascript=
function getPromiseToObservable(myPromise) {
return new Observable(observer => {
myPromise
.then(data => observer.next(data))
.catch(err => observer.error(err))
.finally(() => observer.complete())
});
}
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Hello! World!');
}, 3000)
})
getPromiseToObservable(myPromise).subscribe({
next: data => console.log(data),
complete : () => console.log('complete'),
error: (error) => console.log(error)
})
```
### HttpRequest Observable
```javascript=
function getObservable(url) {
return new Observable(observer => {
const xhr = new XMLHttpRequest();
xhr.addEventListener('load', () => {
if (xhr.readyState === 4 && xhr.status === 200) {
observer.next(JSON.parse(xhr.responseText));
observer.complete();
}
});
xhr.open('GET', url);
xhr.send();
return () => xhr.abort()
})
}
const url = 'https://jsonplaceholder.typicode.com/todos';
const sub = getObservable(url).subscribe({
next: data => console.log(data),
complete : () => console.log('complete'),
error: (error) => console.log(error)
});
sub.unsubscribe();
```
* 練習3. 建立一個可產生倒數N秒Observable的function。
```javascript=
function getCountTimeObservable(max) {
}
getCountTimeObservable(10).subscribe(data => console.log(data));
```
## promise array fromEvent http
* fromEvent
```javascript=
import { fromEvent } from 'rxjs';
const clicks = fromEvent(document, 'click');
clicks.subscribe(x => console.log(x));
```
* array
```javascript=
const arr = [1,2,3,4,5,6,7]
from(arr)
.pipe(
filter(e => e > 3),
toArray()
)
.subscribe(e => console.log(e));
```
* Promise
```javascript=
const promise$ = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success');
}, 3000);
});
from(promise$).subscribe(e => console.log(e));
```
* Http
```javascript=
this.http.get('https://jsonplaceholder.typicode.com/todos').subscribe({
next: data => console.log(data),
complete: () => console.log('complete'),
error: error => console.log(error)
});
```
## Operators
### Operators - merge, concat, tap, filter, map, take
創建類(creation)
轉化類(transformation)
過濾類(filtering)
合併類(combination)
多播類(multicasting)
錯誤處理類(error Handling)
輔助工具類(utility)
條件分支類型(conditional&boolean)
數學和合計類(mathmatical&aggregate)
* of
將引數轉換為Observable
```
of('Hello').subscribe(res => console.log(res));
```
* from
從數列、Promise、iterator等等物件建立一個Observable
```
from([1, 2, 3, 4, 5]).subscribe(res => console.log(res));
```
* map
其實與javascript相同,是資料的映射。
```
const source$ = of(1,2,3,4,5);
source$
.pipe(
map(element => element + 1)
)
.subscribe(element => console.log)
```
* filter
其實與javascript相同,是資料的過濾。
```
const source$ = of(1,2,3,4,5,6);
source$
.pipe(
filter(element => element > 3)
)
.subscribe(element => console.log(element));
```
* tap
監聽,並不影響上游下游資料。
```
const source$ = of(1,2,3,4,5,6);
source$
.pipe(
tap(e => console.log(e))
)
.subscribe(element => console.log(element));
```
* take
就是取得,從上游Observable拿資料,拿夠了就會complete,拿多少才夠就由take決定。且只有取得足夠的數量後,就會立刻complete。
```
const timer$ = interval(1000);
timer$
.pipe(take(1))
.subscribe({
next: (data) => console.log(data),
complete: () => console.log('complete')
});
```
* concat
與javascript的array concat相似,可以合併兩個或多個Observable。
```
const arr1$ = of(1,2,3,4,5);
const arr2$ = of(6,7,8,9,10);
const arr3$ = concat(arr1$, arr2$);
arr3$.subscribe(element => console.log(element));
```
* merge
merge與concat用法很相似,但功能很不一樣,merge會第一時間訂閱所有上游的Observable,採先到先得的策略。
```
const timer1$ = timer(0, 1000);
const timer2$ = timer(500, 1000);
const merge$ = merge(timer1$, timer2$);
merge$.subscribe(data => console.log(data))
```
* 練習4. 將陣列`[1,2,3,4,5]`每一個乘三並留下大於五的數。
```javascript=
const arr = [1,2,3,4,5];
```
### High Order Operators - switchmap, concatmap, mergemap
處理巢狀Obersable
```javascript=
let postApiUrl = 'https://jsonplaceholder.typicode.com/posts/1';
let commentApiUrl = 'https://jsonplaceholder.typicode.com/comments';
this.http.get(postApiUrl).subscribe((p: any) => {
this.http.get(`${commentApiUrl}/${p.id}`).subscribe(c => {
console.log(c);
})
});
```
```javascript=
const timer1$ = timer(2000).pipe(tap(() => console.log('2000OK')));
const timer2$ = timer(5000).pipe(tap(() => console.log('5000OK')));
const timer3$ = timer(1000).pipe(tap(() => console.log('1000OK')));
const arr = [timer1$, timer2$, timer3$];
```
* 取得每一個post的comments
```javascript=
let postApiUrl = 'https://jsonplaceholder.typicode.com/posts';
let commentApiUrl = 'https://jsonplaceholder.typicode.com/comments';
```
## Subject

```javascript=
import { Subject } from 'rxjs';
const subject = new Subject<number>();
subject.subscribe({
next: (v) => console.log(`observerA: ${v}`)
});
subject.subscribe({
next: (v) => console.log(`observerB: ${v}`)
});
subject.next(1);
subject.next(2);
// Logs:
// observerA: 1
// observerB: 1
// observerA: 2
// observerB: 2
```
比較(單一訊號多個訂閱)
```javascript=
const timer1 = interval(2000).pipe(take(5));
timer1.subscribe(x => console.log(x));
setTimeout(() => {
timer1.subscribe(x => console.log(`%c三秒後 ${x}`, 'color: red'));
}, 3000);
```
subject
```javascript=
const timer$ = interval(1000).pipe(take(5));
const subject = new Subject();
timer$.subscribe(subject);
subject.subscribe(x => console.log(x));
setTimeout(() => {
subject.subscribe(x => console.log(`%c三秒後 ${x}`, 'color: red'));
}, 3000);
```