Slide 1

Slide 1 text

輕鬆應付複雜的⾮非同步操作 RxJS + Redux Observable Huli

Slide 2

Slide 2 text

胡⽴立 Hu Li • 東南亞新創公司 Eatigo Frontend Team Lead • TechBridge Weekly 技術週刊編輯 
 致⼒力於淺顯易懂的程式教學,搞不好你看過我的⽂文章 • [⼼心得] ⼗十年程式⾃自學之路 • 零基礎的⼩小明要如何成為前端⼯工程師? • 跟著⼩小明⼀一起搞懂技術名詞:MVC、SPA 與 SSR • 資料庫的好夥伴:Redis • 讓我們來談談 CSRF • 希望是最淺顯易懂的 RxJS 教學 [email protected]

Slide 3

Slide 3 text

開始之前 4⽉月底 RxJS 5 => RxJS 6
 6⽉月底 redux-observable v0.x => v1.0.0 由於概念不變,簡報中舉的例⼦子都是
 RxJS5 + redux-observable v0.x

Slide 4

Slide 4 text

什麼是 ReactiveX?

Slide 5

Slide 5 text

與其讓我告訴你那是什麼 不如你⾃自⼰己體會

Slide 6

Slide 6 text

⼩小試⾝身⼿手 現在有⼀一個陣列,裡⾯面每個元素都是⾃自然數
 請你把奇數挑出來,並且平⽅方之後加總

Slide 7

Slide 7 text

const solution = arr => {
 let ans = 0
 for(let i=0; i

Slide 8

Slide 8 text

有沒有其他解法?

Slide 9

Slide 9 text

const solution = arr => {
 return arr
 .filter(val => val % 2)
 .map(val => val * val)
 .reduce(
 (total, val) => total + val
 )
 }

Slide 10

Slide 10 text

const solution = arr => {
 let ans = 0
 for(let i=0; i {
 return arr
 .filter(val => val % 2)
 .map(val => val * val)
 .reduce(
 (total, val) => total + val
 )
 }

Slide 11

Slide 11 text

const solution = arr => {
 let ans = 0
 for(let i=0; i {
 return arr
 .filter(val => val % 2)
 .map(val => val * val)
 .reduce(
 (total, val) => total + val
 )
 } Imperative 命令式 什麼東⻄西都⾃自⼰己來,⼼心 hen 累

Slide 12

Slide 12 text

const solution = arr => {
 let ans = 0
 for(let i=0; i {
 return arr
 .filter(val => val % 2)
 .map(val => val * val)
 .reduce(
 (total, val) => total + val
 )
 } Imperative 命令式 什麼東⻄西都⾃自⼰己來,⼼心 hen 累 Declarative 聲明式 告訴電腦:我想要什麼

Slide 13

Slide 13 text

⼩小試⾝身⼿手 現在有兩堆資料 orders 跟 members
 請找出⾦金額 > 100 且 id > 10 的資料
 並且加上會員資料⼀一併回傳

Slide 14

Slide 14 text

const solution = (orders, members) => {
 const ans = []
 for(let i=0; i 100 && orders[i].id > 10) {
 ans.push({
 order: orders[i],
 member: members[orders.memberId]
 })
 }
 }
 return ans
 } Imperative 命令式 什麼東⻄西都⾃自⼰己來,⼼心 hen 累

Slide 15

Slide 15 text

const solution = (orders, members) => {
 const ans = []
 for(let i=0; i 100 && orders[i].id > 10) {
 ans.push({
 order: orders[i],
 member: members[orders.memberId]
 })
 }
 }
 return ans
 } const solution = (orders, members) => {
 return orders
 .filter(o => o.amount > 100)
 .filter(o => o.id > 10)
 .map(o => ({
 order: o,
 member: members[o.memberId] 
 }))
 } Imperative 命令式 什麼東⻄西都⾃自⼰己來,⼼心 hen 累 Declarative 聲明式 告訴電腦:我想要什麼

Slide 16

Slide 16 text

其實你早就學過 Declarative

Slide 17

Slide 17 text

const solution = (orders, members) => {
 const ans = []
 for(let i=0; i 100 && orders[i].id > 10) {
 ans.push({
 order: orders[i],
 member: members[orders.memberId]
 })
 }
 }
 return ans
 } SELECT * FROM orders
 WHERE orders.amount > 100 AND orders.id > 10
 JOIN members ON members.id = orders.memberId Imperative 命令式 什麼東⻄西都⾃自⼰己來,⼼心 hen 累 Declarative 聲明式 告訴電腦:我想要什麼

Slide 18

Slide 18 text

把資料經過⼀一連串轉換
 得出想要的結果

Slide 19

Slide 19 text

1 2 3 4 5 6

Slide 20

Slide 20 text

1 2 3 4 5 6 filter (取奇數)

Slide 21

Slide 21 text

1 2 3 4 5 6 filter (取奇數) 1 3 5

Slide 22

Slide 22 text

1 2 3 4 5 6 filter (取奇數) 1 3 5 map (平⽅方)

Slide 23

Slide 23 text

1 2 3 4 5 6 filter (取奇數) 1 3 5 map (平⽅方) 1 9 25

Slide 24

Slide 24 text

1 2 3 4 5 6 filter (取奇數) 1 3 5 map (平⽅方) 1 9 25 reduce (加總)

Slide 25

Slide 25 text

1 2 3 4 5 6 filter (取奇數) 1 3 5 map (平⽅方) 1 9 25 reduce (加總) 35

Slide 26

Slide 26 text

重點⼀一
 
 把資料經過⼀一連串轉換
 得出想要的結果

Slide 27

Slide 27 text

Reactive
 反應、反應性的

Slide 28

Slide 28 text

window.addEventListener(
 'click',() => { console.log('click!') }
 )

Slide 29

Slide 29 text

重點⼆二
 當某事發⽣生,我能做出反應

Slide 30

Slide 30 text

1. 某事發⽣生,我能做出反應
 2. 把資料經過轉換得出答案

Slide 31

Slide 31 text

Rx.Observable.fromEvent(window, 'click')
 .map(e => e.target)
 .subscribe(value => { console.log(‘click!’, value) }
 )

Slide 32

Slide 32 text

Rx.Observable.fromEvent(window, 'click')
 .map(e => e.target)
 .subscribe(value => { console.log(‘click!’, value) }
 ) 當某事發⽣生,我能做出反應

Slide 33

Slide 33 text

Rx.Observable.fromEvent(window, 'click')
 .map(e => e.target)
 .subscribe(value => { console.log(‘click!’, value) }
 ) 把資料經過轉換

Slide 34

Slide 34 text

Rx.Observable.fromEvent(window, 'click')
 .map(e => e.target)
 .subscribe(value => { console.log(‘click!’, value) }
 ) 得出我想要的答案

Slide 35

Slide 35 text

Rx.Observable.fromEvent(window, 'click')
 .map(e => e.target)
 .subscribe(value => { console.log(‘click!’, value) }
 )

Slide 36

Slide 36 text

Observable 可被觀察的
 
 可以想成 stream 資料流
 會隨著時間增加元素的陣列

Slide 37

Slide 37 text

Rx.Observable.fromEvent(window, 'click')
 .map(e => 1)
 .scan((total, val) => total + val)
 .subscribe(value => { console.log(‘click!’, value) }
 )

Slide 38

Slide 38 text

click map scan (加總)

Slide 39

Slide 39 text

click map scan (加總) 1

Slide 40

Slide 40 text

click map scan (加總) 1 1

Slide 41

Slide 41 text

click click map scan (加總) 1 1

Slide 42

Slide 42 text

click click map scan (加總) 1 1 1

Slide 43

Slide 43 text

click click map scan (加總) 1 1 1 2

Slide 44

Slide 44 text

click click click map scan (加總) 1 1 1 2

Slide 45

Slide 45 text

click click click map scan (加總) 1 1 1 1 2

Slide 46

Slide 46 text

click click click map scan (加總) 1 1 1 1 2 3

Slide 47

Slide 47 text

RxJS 的威⼒力所在:組合技


Slide 48

Slide 48 text

兩個陣列合併會變什麼?

Slide 49

Slide 49 text

[1, 2, 3] 跟 [4, 5, 6]
 
 串接:[1, 2, 3, 4, 5, 6]
 相加:[5, 7, 9]

Slide 50

Slide 50 text

兩個 Observable 合併會變什麼?

Slide 51

Slide 51 text

兩個 Observable 合併會變什麼? 會隨著時間增加元素的陣列

Slide 52

Slide 52 text

10 40 7 30 5 merge 10 40 7 30 5

Slide 53

Slide 53 text

可以把兩個資料流合併!

Slide 54

Slide 54 text

Rx.Observable.fromEvent(plus, 'click')
 .mapTo(1)
 .merge(
 Rx.Observable.fromEvent(minus, 'click')
 .mapTo(-1)
 )
 .scan((total, val) => total + val)
 .subscribe(value => { console.log(‘click!’, value) }
 ) +1 -1

Slide 55

Slide 55 text

Rx.Observable.fromEvent(plus, 'click')
 .mapTo(1)
 .merge(
 Rx.Observable.fromEvent(minus, 'click')
 .mapTo(-1)
 )
 .scan((total, val) => total + val)
 .subscribe(value => { console.log(‘click!’, value) }
 ) merge +1 -1

Slide 56

Slide 56 text

Rx.Observable.fromEvent(plus, 'click')
 .mapTo(1)
 .merge(
 Rx.Observable.fromEvent(minus, 'click')
 .mapTo(-1)
 )
 .scan((total, val) => total + val)
 .subscribe(value => { console.log(‘click!’, value) }
 ) +1 merge +1 -1

Slide 57

Slide 57 text

Rx.Observable.fromEvent(plus, 'click')
 .mapTo(1)
 .merge(
 Rx.Observable.fromEvent(minus, 'click')
 .mapTo(-1)
 )
 .scan((total, val) => total + val)
 .subscribe(value => { console.log(‘click!’, value) }
 ) +1 merge +1 +1 -1

Slide 58

Slide 58 text

Rx.Observable.fromEvent(plus, 'click')
 .mapTo(1)
 .merge(
 Rx.Observable.fromEvent(minus, 'click')
 .mapTo(-1)
 )
 .scan((total, val) => total + val)
 .subscribe(value => { console.log(‘click!’, value) }
 ) +1 +1 merge +1 +1 -1

Slide 59

Slide 59 text

Rx.Observable.fromEvent(plus, 'click')
 .mapTo(1)
 .merge(
 Rx.Observable.fromEvent(minus, 'click')
 .mapTo(-1)
 )
 .scan((total, val) => total + val)
 .subscribe(value => { console.log(‘click!’, value) }
 ) +1 +1 merge +1 +1 +1 -1

Slide 60

Slide 60 text

Rx.Observable.fromEvent(plus, 'click')
 .mapTo(1)
 .merge(
 Rx.Observable.fromEvent(minus, 'click')
 .mapTo(-1)
 )
 .scan((total, val) => total + val)
 .subscribe(value => { console.log(‘click!’, value) }
 ) +1 +1 -1 merge +1 +1 +1 -1

Slide 61

Slide 61 text

Rx.Observable.fromEvent(plus, 'click')
 .mapTo(1)
 .merge(
 Rx.Observable.fromEvent(minus, 'click')
 .mapTo(-1)
 )
 .scan((total, val) => total + val)
 .subscribe(value => { console.log(‘click!’, value) }
 ) +1 +1 -1 merge +1 +1 -1 +1 -1

Slide 62

Slide 62 text

Rx.Observable.fromEvent(plus, 'click')
 .mapTo(1)
 .merge(
 Rx.Observable.fromEvent(minus, 'click')
 .mapTo(-1)
 )
 .scan((total, val) => total + val)
 .subscribe(value => { console.log(‘click!’, value) }
 ) +1 +1 +1 -1 merge +1 +1 -1 +1 -1

Slide 63

Slide 63 text

Rx.Observable.fromEvent(plus, 'click')
 .mapTo(1)
 .merge(
 Rx.Observable.fromEvent(minus, 'click')
 .mapTo(-1)
 )
 .scan((total, val) => total + val)
 .subscribe(value => { console.log(‘click!’, value) }
 ) +1 +1 +1 -1 merge +1 +1 +1 -1 +1 -1

Slide 64

Slide 64 text

Rx.Observable.fromEvent(plus, 'click')
 .mapTo(1)
 .merge(
 Rx.Observable.fromEvent(minus, 'click')
 .mapTo(-1)
 )
 .scan((total, val) => total + val)
 .subscribe(value => { console.log(‘click!’, value) }
 ) +1 +1 +1 -1 -1 merge +1 +1 +1 -1 +1 -1

Slide 65

Slide 65 text

Rx.Observable.fromEvent(plus, 'click')
 .mapTo(1)
 .merge(
 Rx.Observable.fromEvent(minus, 'click')
 .mapTo(-1)
 )
 .scan((total, val) => total + val)
 .subscribe(value => { console.log(‘click!’, value) }
 ) +1 +1 +1 -1 -1 merge +1 +1 +1 -1 -1 +1 -1

Slide 66

Slide 66 text

Rx.Observable.fromEvent(plus, 'click')
 .mapTo(1)
 .merge(
 Rx.Observable.fromEvent(minus, 'click')
 .mapTo(-1)
 )
 .scan((total, val) => total + val)
 .subscribe(value => { console.log(‘click!’, value) }
 ) var total = 0 plus.addEventListener('click', () => {
 total++
 console.log(‘click!’, total)
 })
 
 minus.addEventListener('click', () => {
 total—
 console.log(‘click!’, total)
 })

Slide 67

Slide 67 text

RxJS 與 Promise

Slide 68

Slide 68 text

當某事發⽣生,我能做出反應

Slide 69

Slide 69 text

當某事發⽣生,我能做出反應 當 Promise 完成時

Slide 70

Slide 70 text

function getData() {
 return fetch('http://example.com/api')
 .then(res => res.json()) 
 }
 
 Rx.Observable.fromPromise(getData())
 .subscribe(response => { console.log(response) }
 )

Slide 71

Slide 71 text

Rx.Observable.fromEvent(window, 'click')
 .map(e =>
 Rx.Observable.fromPromise(getData())
 )
 .subscribe(response => { console.log(response) })

Slide 72

Slide 72 text

Rx.Observable.fromEvent(window, 'click')
 .map(e =>
 Rx.Observable.fromPromise(getData())
 )
 .subscribe(response => { console.log(response) })

Slide 73

Slide 73 text

click Rx.Observable.fromEvent(window, 'click')
 .map(e =>
 Rx.Observable.fromPromise(getData())
 )
 .subscribe(response => { console.log(response) })

Slide 74

Slide 74 text

click Rx.Observable.fromEvent(window, 'click')
 .map(e =>
 Rx.Observable.fromPromise(getData())
 )
 .subscribe(response => { console.log(response) }) click Observable response

Slide 75

Slide 75 text

[[1, 2], [3, 4, 5], [6], 7]
 想把陣列壓平怎麼辦?

Slide 76

Slide 76 text

_.flatten(arr)

Slide 77

Slide 77 text

click Rx.Observable.fromEvent(window, 'click')
 .map(e =>
 Rx.Observable.fromPromise(getData())
 )
 .subscribe(response => { console.log(response) }) click Observable response

Slide 78

Slide 78 text

click Rx.Observable.fromEvent(window, 'click')
 .flatMap(e =>
 Rx.Observable.fromPromise(getData())
 )
 .subscribe(response => { console.log(response) }) click response

Slide 79

Slide 79 text

click Rx.Observable.fromEvent(window, 'click')
 .flatMap(e =>
 Rx.Observable.fromPromise(getData())
 )
 .subscribe(response => { console.log(response) }) click response click

Slide 80

Slide 80 text

click Rx.Observable.fromEvent(window, 'click')
 .flatMap(e =>
 Rx.Observable.fromPromise(getData())
 )
 .subscribe(response => { console.log(response) }) click response click response

Slide 81

Slide 81 text

mergeMap(flatMap)
 concatMap
 switchMap

Slide 82

Slide 82 text

mergeMap 很簡單,就是 parallel

Slide 83

Slide 83 text

mergeMap 很簡單,就是 parallel click request

Slide 84

Slide 84 text

mergeMap 很簡單,就是 parallel click request request

Slide 85

Slide 85 text

mergeMap 很簡單,就是 parallel click request request request

Slide 86

Slide 86 text

mergeMap 很簡單,就是 parallel click request response request request

Slide 87

Slide 87 text

mergeMap 很簡單,就是 parallel click request response request request response

Slide 88

Slide 88 text

mergeMap 很簡單,就是 parallel click request response request response request response

Slide 89

Slide 89 text

mergeMap 很簡單,就是 parallel
 response 順序不代表 request 的順序 click request response request response request response

Slide 90

Slide 90 text

mergeMap 可以限制 concurrency 數量
 假設設定為 1

Slide 91

Slide 91 text

mergeMap 可以限制 concurrency 數量
 假設設定為 1 click request

Slide 92

Slide 92 text

mergeMap 可以限制 concurrency 數量
 假設設定為 1 click request response

Slide 93

Slide 93 text

mergeMap 可以限制 concurrency 數量
 假設設定為 1 click request response request

Slide 94

Slide 94 text

mergeMap 可以限制 concurrency 數量
 假設設定為 1 click request response request response

Slide 95

Slide 95 text

mergeMap 可以限制 concurrency 數量
 假設設定為 1 click request response request response request

Slide 96

Slide 96 text

mergeMap 可以限制 concurrency 數量
 假設設定為 1 click request response response request response request

Slide 97

Slide 97 text

concatMap 執⾏行完⼀一個才會執⾏行下⼀一個
 就是 mergeMap(… , 1) click request response response request response request

Slide 98

Slide 98 text

switchMap 只取最後⼀一個 request 的 response
 是很常⽤用的⼀一個⽅方法 click request response response request response request

Slide 99

Slide 99 text

No content

Slide 100

Slide 100 text

A 餐廳

Slide 101

Slide 101 text

A 餐廳 回上⼀一⾴頁

Slide 102

Slide 102 text

A 餐廳 回上⼀一⾴頁 B 餐廳

Slide 103

Slide 103 text

⾮非同步的問題 假設 A 的 response 花了 3 秒
 B 的 response 只花了 1 秒

Slide 104

Slide 104 text

⾮非同步的問題 假設 A 的 response 花了 3 秒
 B 的 response 只花了 1 秒 mergeMap:1 秒後顯⽰示 B,3 秒後顯⽰示 A

Slide 105

Slide 105 text

⾮非同步的問題 假設 A 的 response 花了 3 秒
 B 的 response 只花了 1 秒 mergeMap:1 秒後顯⽰示 B,3 秒後顯⽰示 A
 concatMap:3 秒後顯⽰示 A,4 秒後顯⽰示 B


Slide 106

Slide 106 text

⾮非同步的問題 假設 A 的 response 花了 3 秒
 B 的 response 只花了 1 秒 mergeMap:1 秒後顯⽰示 B,3 秒後顯⽰示 A
 concatMap:3 秒後顯⽰示 A,4 秒後顯⽰示 B
 switchMap:1 秒後顯⽰示 B

Slide 107

Slide 107 text

⾮非同步的問題 假設 A 的 response 花了 3 秒
 B 的 response 只花了 1 秒 mergeMap:1 秒後顯⽰示 B,3 秒後顯⽰示 A
 concatMap:3 秒後顯⽰示 A,4 秒後顯⽰示 B
 switchMap:1 秒後顯⽰示 B 很常⽤用的原因:只要管最新的 request

Slide 108

Slide 108 text

Redux 與 RxJS

Slide 109

Slide 109 text

核⼼心概念:action in, action out
 在 middleware ⽤用 RxJS 解決⼀一切 redux-observable Action
 getUser middleware epic Action
 setUser API Server

Slide 110

Slide 110 text

redux-observable

Slide 111

Slide 111 text

const getUserEpic = action$ => action$.ofType(actionTypes.GET_USER)
 .switchMap(action => 
 Rx.Observable.fromPromise(API.getUser(
 action.payload.id
 ))
 .map(user => setUser(user))
 .catch(err => Observable.of(getUserError(err)))
 )
 Epic

Slide 112

Slide 112 text

const getUserEpic = action$ => action$.ofType(actionTypes.GET_USER)
 .switchMap(action => 
 Rx.Observable.fromPromise(API.getUser(
 action.payload.id
 ))
 .map(user => setUser(user))
 .catch(err => Observable.of(getUserError(err)))
 )
 Action in, Action out

Slide 113

Slide 113 text

與其他 Middleware 的⽐比較

Slide 114

Slide 114 text

dispatch ⼀一個 promise,middleware 幫你執⾏行 redux-promise const getUser = id => ({
 type:'GET_USER',
 payload:API.getUser(id)
 }) middleware redux-promise GET_USER_PENDING
 GET_USER_FULFILLED
 GET_USER_REJECTED API Server

Slide 115

Slide 115 text

dispatch ⼀一個 function,middleware 幫你執⾏行 redux-thunk const getUser = id => { return dispatch => { return API.getUser(id).then( user => dispatch(setUser(user)) ).catch( err => dispatch(getUserError(err)) ) } }

Slide 116

Slide 116 text

與 redux-observable 類似,都是 dispatch pure action
 其餘的讓 saga 去處理 redux-saga Action
 getUser middleware saga Action
 setUser API Server

Slide 117

Slide 117 text

監聽指定的 action,並且丟給 worker 處理 watcher function* watcherSaga() { yield takeLatest("GET_USER", fetchUser) } takeLatest:只拿最後⼀一個(switchMap)
 takeEvery:每個都拿(mergeMap)

Slide 118

Slide 118 text

call API 並且 dispatch 結果 worker function* fetchUser(action) { try { const user = yield call(
 API.getUser, action.payload.id
 ) yield put(setUser(user)) } catch (err) { yield put(getUserError(err)) } }

Slide 119

Slide 119 text

簡單⽐比較 promise thunk saga observable 學習難度 ☆ ☆☆ ☆☆☆☆☆ ☆☆☆☆☆ Action 型態 Promise Function Object Object ⾮非同步
 流程管理 X X O O ⽅方便測試 X X O O

Slide 120

Slide 120 text

RxJS 與各種複雜情境

Slide 121

Slide 121 text

情境⼀一:巢狀 API /api/posts/:id
 /api/posts/:id/translations

Slide 122

Slide 122 text

情境⼀一:巢狀 API /api/posts/:id
 /api/posts/:id/translations const epic = action$ => action$.ofType(actionTypes.GET_POST)
 .switchMap(action =>
 Observable.forkJoin( Observable.from(API.getPost(id)),
 Observable.from(API.getTrans(id))
 )
 .map(data => Actions.setPost({
 post: data[0],
 translations: data[1]
 })
 )
 )

Slide 123

Slide 123 text

情境⼀一:巢狀 API /api/posts/:id
 /api/posts/:id/translations const epic = action$ => action$.ofType(actionTypes.GET_POST)
 .switchMap(action =>
 Observable.forkJoin( Observable.from(API.getPost(id)),
 Observable.from(API.getTrans(id))
 )
 .map(data => Actions.setPost({
 post: data[0],
 translations: data[1]
 })
 )
 )

Slide 124

Slide 124 text

情境⼆二:call 兩個 API /api/posts/:id
 /api/users/:id

Slide 125

Slide 125 text

情境⼆二:call 兩個 API /api/posts/:id
 /api/users/:id action$.ofType(GET_POST).switchMap(action => Observable.from(API.getPost(action.id))
 .mergeMap(post => Observable.from(API.getUser(post.authorId))
 .map(user => Actions.setPost({
 post,
 user
 })
 )
 ) 
 )

Slide 126

Slide 126 text

情境⼆二:call 兩個 API /api/posts/:id
 /api/users/:id action$.ofType(GET_POST).switchMap(action => Observable.from(API.getPost(action.id))
 .mergeMap(post => Observable.from(API.getUser(post.authorId))
 .map(user => Actions.setPost({
 post,
 user
 })
 )
 ) 
 )

Slide 127

Slide 127 text

情境三:call ⼀一⼤大堆 API /api/posts
 /api/users/:id

Slide 128

Slide 128 text

情境三:call ⼀一⼤大堆 API action$.ofType(GET_POSTS).switchMap(action => Observable.from(API.getPosts())
 .switchMap(posts =>
 Observable.of(...posts)
 .mergeMap(item =>
 Observable.from(API.getUser(item.authorId))
 .map(user => ({ ...item,
 user
 }))
 , 10)
 )
 )
 .toArray()
 .map(posts => Actions.setPost(posts)
 )

Slide 129

Slide 129 text

情境三:call ⼀一⼤大堆 API action$.ofType(GET_POSTS).switchMap(action => Observable.from(API.getPosts())
 .switchMap(posts =>
 Observable.of(...posts)
 .mergeMap(item =>
 Observable.from(API.getUser(item.authorId))
 .map(user => ({ ...item,
 user
 }))
 , 10)
 )
 )
 .toArray()
 .map(posts => Actions.setPost(posts)
 )

Slide 130

Slide 130 text

情境三:call ⼀一⼤大堆 API action$.ofType(GET_POSTS).switchMap(action => Observable.from(API.getPosts())
 .switchMap(posts =>
 Observable.of(...posts)
 .mergeMap(item =>
 Observable.from(API.getUser(item.authorId))
 .map(user => ({ ...item,
 user
 }))
 , 10)
 )
 )
 .toArray()
 .map(posts => Actions.setPost(posts)
 )

Slide 131

Slide 131 text

情境三:call ⼀一⼤大堆 API action$.ofType(GET_POSTS).switchMap(action => Observable.from(API.getPosts())
 .switchMap(posts =>
 Observable.of(...posts)
 .mergeMap(item =>
 Observable.from(API.getUser(item.authorId))
 .map(user => ({ ...item,
 user
 }))
 , 10)
 )
 )
 .toArray()
 .map(posts => Actions.setPost(posts)
 )

Slide 132

Slide 132 text

情境三:call ⼀一⼤大堆 API action$.ofType(GET_POSTS).switchMap(action => Observable.from(API.getPosts())
 .switchMap(posts =>
 Observable.of(...posts)
 .mergeMap(item =>
 Observable.from(API.getUser(item.authorId))
 .map(user => ({ ...item,
 user
 }))
 , 10)
 )
 )
 .toArray()
 .map(posts => Actions.setPost(posts)
 )

Slide 133

Slide 133 text

Q&A [email protected]
 https://medium.com/@hulitw