30 天精通 RxJS(18): Observable Operators - switchMap, mergeMap, concatMap
今天我們要講三個非常重要的 operators,這三個 operators 在很多的 RxJS 相關(guān)的 library 的使用范例上都會看到俺叭。很多初學(xué)者在使用這些 library 時湖饱,看到這三個 operators 很可能就放棄了贝乎,但其實如果有把這個系列的文章完整看過的話捻爷,現(xiàn)在應(yīng)該就能很好接受跟理解号显。
Operators
concatMap
concatMap 其實就是 map 加上 concatAll 的簡化寫法嘿架,我們直接來看一個范例
var source = Rx.Observable.fromEvent(document.body, 'click');
var example = source
.map(e => Rx.Observable.interval(1000).take(3))
.concatAll();
example.subscribe({
next: (value) => { console.log(value); },
error: (err) => { console.log('Error: ' + err); },
complete: () => { console.log('complete'); }
});
上面這個范例就可以簡化成
var source = Rx.Observable.fromEvent(document.body, 'click');
var example = source
.concatMap(
e => Rx.Observable.interval(100).take(3)
);
example.subscribe({
next: (value) => { console.log(value); },
error: (err) => { console.log('Error: ' + err); },
complete: () => { console.log('complete'); }
});
前后兩個行為是一致的松捉,記得 concatMap 也會先處理前一個送出的 observable 在處理下一個 observable涩赢,畫成 Marble Diagram 如下
source : -----------c--c------------------...
concatMap(c => Rx.Observable.interval(100).take(3))
example: -------------0-1-2-0-1-2---------...
這樣的行為也很常被用在發(fā)送 request 如下
function getPostData() {
return fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(res => res.json())
}
var source = Rx.Observable.fromEvent(document.body, 'click');
var example = source.concatMap(
e => Rx.Observable.from(getPostData()));
example.subscribe({
next: (value) => { console.log(value); },
error: (err) => { console.log('Error: ' + err); },
complete: () => { console.log('complete'); }
});
這裡我們每點擊一下畫面就會送出一個 HTTP request戈次,如果我們快速的連續(xù)點擊,大家可以在開發(fā)者工具的 network 看到每個 request 是等到前一個 request 完成才會送出下一個 request筒扒,如下圖
這裡建議把網(wǎng)速模擬調(diào)到最慢
[圖片上傳中...(image-e29f3b-1526857283270-3)]
從 network 的圖形可以看得出來怯邪,第二個 request 的發(fā)送時間是接在第一個 request 之后的,我們可以確保每一個 request 會等前一個 request 完成才做處理花墩。
concatMap 還有第二個參數(shù)是一個 selector callback悬秉,這個 callback 會傳入四個參數(shù),分別是
- 外部 observable 送出的元素
- 內(nèi)部 observable 送出的元素
- 外部 observable 送出元素的 index
- 內(nèi)部 observable 送出元素的 index
回傳值我們想要的值冰蘑,范例如下
function getPostData() {
return fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(res => res.json())
}
var source = Rx.Observable.fromEvent(document.body, 'click');
var example = source.concatMap(
e => Rx.Observable.from(getPostData()),
(e, res, eIndex, resIndex) => res.title);
example.subscribe({
next: (value) => { console.log(value); },
error: (err) => { console.log('Error: ' + err); },
complete: () => { console.log('complete'); }
});
這個范例的外部 observable 送出的元素就是 click event 物件和泌,內(nèi)部 observable 送出的元素就是 response 物件,這裡我們回傳 response 物件的 title 屬性祠肥,這樣一來我們就可以直接收到 title武氓,這個方法很適合用在 response 要選取的值跟前一個事件或順位(index)相關(guān)時。
switchMap
switchMap 其實就是 map 加上 switch 簡化的寫法仇箱,如下
var source = Rx.Observable.fromEvent(document.body, 'click');
var example = source
.map(e => Rx.Observable.interval(1000).take(3))
.switch();
example.subscribe({
next: (value) => { console.log(value); },
error: (err) => { console.log('Error: ' + err); },
complete: () => { console.log('complete'); }
});
上面的程式碼可以簡化成
var source = Rx.Observable.fromEvent(document.body, 'click');
var example = source
.switchMap(
e => Rx.Observable.interval(100).take(3)
);
example.subscribe({
next: (value) => { console.log(value); },
error: (err) => { console.log('Error: ' + err); },
complete: () => { console.log('complete'); }
});
畫成 Marble Diagram 表示如下
source : -----------c--c-----------------...
concatMap(c => Rx.Observable.interval(100).take(3))
example: -------------0--0-1-2-----------...
只要注意一個重點 switchMap 會在下一個 observable 被送出后直接退訂前一個未處理完的 observable县恕,這個部份的細(xì)節(jié)請看上一篇文章 switch 的部分。
另外我們也可以把 switchMap 用在發(fā)送 HTTP request
function getPostData() {
return fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(res => res.json())
}
var source = Rx.Observable.fromEvent(document.body, 'click');
var example = source.switchMap(
e => Rx.Observable.from(getPostData()));
example.subscribe({
next: (value) => { console.log(value); },
error: (err) => { console.log('Error: ' + err); },
complete: () => { console.log('complete'); }
});
如果我們快速的連續(xù)點擊五下剂桥,可以在開發(fā)者工具的 network 看到每個 request 會在點擊時發(fā)送忠烛,如下圖
灰色是瀏覽器原生地停頓行為,實際上灰色的一開始就是 fetch 執(zhí)行送出 request渊额,只是卡在瀏覽器等待發(fā)送况木。
從上圖可以看到垒拢,雖然我們發(fā)送了多個 request 但最后真正印出來的 log 只會有一個旬迹,代表前面發(fā)送的 request 已經(jīng)不會造成任何的 side-effect 了,這個很適合用在只看最后一次 request 的情境求类,比如說 自動完成(auto complete)奔垦,我們只需要顯示使用者最后一次打在畫面上的文字,來做建議選項而不用每一次的尸疆。
switchMap 跟 concatMap 一樣有第二個參數(shù) selector callback 可用來回傳我們要的值椿猎,這部分的行為跟 concatMap 是一樣的惶岭,這裡就不再贅述。
mergeMap
mergeMap 其實就是 map 加上 mergeAll 簡化的寫法犯眠,如下
var source = Rx.Observable.fromEvent(document.body, 'click');
var example = source
.map(e => Rx.Observable.interval(1000).take(3))
.mergeAll();
example.subscribe({
next: (value) => { console.log(value); },
error: (err) => { console.log('Error: ' + err); },
complete: () => { console.log('complete'); }
});
上面的程式碼可以簡化成
var source = Rx.Observable.fromEvent(document.body, 'click');
var example = source
.mergeMap(
e => Rx.Observable.interval(100).take(3)
);
example.subscribe({
next: (value) => { console.log(value); },
error: (err) => { console.log('Error: ' + err); },
complete: () => { console.log('complete'); }
});
畫成 Marble Diagram 表示
source : -----------c-c------------------...
concatMap(c => Rx.Observable.interval(100).take(3))
example: -------------0-(10)-(21)-2----------...
記得 mergeMap 可以并行處理多個 observable按灶,以這個例子來說當(dāng)我們快速點按兩下,元素發(fā)送的時間點是有機(jī)會重疊的筐咧,這個部份的細(xì)節(jié)大家可以看上一篇文章 merge 的部分鸯旁。
另外我們也可以把 mergeMap 用在發(fā)送 HTTP request
function getPostData() {
return fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(res => res.json())
}
var source = Rx.Observable.fromEvent(document.body, 'click');
var example = source.mergeMap(
e => Rx.Observable.from(getPostData()));
example.subscribe({
next: (value) => { console.log(value); },
error: (err) => { console.log('Error: ' + err); },
complete: () => { console.log('complete'); }
});
如果我們快速的連續(xù)點擊五下,大家可以在開發(fā)者工具的 network 看到每個 request 會在點擊時發(fā)送并且會 log 出五個物件量蕊,如下圖
mergeMap 也能傳入第二個參數(shù) selector callback铺罢,這個 selector callback 跟 concatMap 第二個參數(shù)也是完全一樣的,但 mergeMap 的重點是我們可以傳入第三個參數(shù)残炮,來限制并行處理的數(shù)量
function getPostData() {
return fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(res => res.json())
}
var source = Rx.Observable.fromEvent(document.body, 'click');
var example = source.mergeMap(
e => Rx.Observable.from(getPostData()),
(e, res, eIndex, resIndex) => res.title, 3);
example.subscribe({
next: (value) => { console.log(value); },
error: (err) => { console.log('Error: ' + err); },
complete: () => { console.log('complete'); }
});
這裡我們傳入 3 就能限制韭赘,HTTP request 最多只能同時送出 3 個,并且要等其中一個完成在處理下一個势就,如下圖
大家可以注意看上面這張圖泉瞻,我連續(xù)點按了五下,但第四個 request 是在第一個完成后才送出的苞冯,這個很適合用在特殊的需求下瓦灶,可以限制同時發(fā)送的 request 數(shù)量。
RxJS 5 還保留了 mergeMap 的別名叫 flatMap抱完,雖然官方文件上沒有贼陶,但這兩個方法是完全一樣的。請參考這裡
switchMap, mergeMap, concatMap
這三個 operators 還有一個共同的特性巧娱,那就是這三個 operators 可以把第一個參數(shù)所回傳的 promise 物件直接轉(zhuǎn)成 observable碉怔,這樣我們就不用再用 Rx.Observable.from
轉(zhuǎn)一次,如下
function getPersonData() {
return fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(res => res.json())
}
var source = Rx.Observable.fromEvent(document.body, 'click');
var example = source.concatMap(e => getPersonData());
//直接回傳 promise 物件
example.subscribe({
next: (value) => { console.log(value); },
error: (err) => { console.log('Error: ' + err); },
complete: () => { console.log('complete'); }
});
至于在使用上要如何選擇這三個 operators禁添? 其實都還是看使用情境而定撮胧,這裡筆者簡單列一下大部分的使用情境
- concatMap 用在可以確定內(nèi)部的 observable 結(jié)束時間比外部 observable 發(fā)送時間來快的情境,并且不希望有任何并行處理行為老翘,適合少數(shù)要一次一次完成到底的的 UI 動畫或特別的 HTTP request 行為芹啥。
- switchMap 用在只要最后一次行為的結(jié)果,適合絕大多數(shù)的使用情境铺峭。
- mergeMap 用在并行處理多個 observable墓怀,適合需要并行處理的行為,像是多個 I/O 的并行處理卫键。
建議初學(xué)者不確定選哪一個時傀履,使用 switchMap
在使用 concatAll 或 concatMap 時,請注意內(nèi)部的 observable 一定要能夠的結(jié)束莉炉,且外部的 observable 發(fā)送元素的速度不能比內(nèi)部的 observable 結(jié)束時間快太多钓账,不然會有 memory issues
今日小結(jié)
今天的文章內(nèi)容主要講了三個 operators碴犬,如果有看完上一篇文章的讀者應(yīng)該不難吸收,主要還是使用情境上需要思考以及注意一些細(xì)節(jié)梆暮。
不知道今天讀者有沒有收穫呢服协? 如果有任何問題,歡迎留言給我啦粹,謝謝