30 天精通 RxJS(18): Observable Operators - switchMap, mergeMap, concatMap

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'); }
});

JSBin | JSFiddle

這裡我們每點擊一下畫面就會送出一個 HTTP request戈次,如果我們快速的連續(xù)點擊,大家可以在開發(fā)者工具的 network 看到每個 request 是等到前一個 request 完成才會送出下一個 request筒扒,如下圖

image.png

這裡建議把網(wǎng)速模擬調(diào)到最慢

[圖片上傳中...(image-e29f3b-1526857283270-3)]

從 network 的圖形可以看得出來怯邪,第二個 request 的發(fā)送時間是接在第一個 request 之后的,我們可以確保每一個 request 會等前一個 request 完成才做處理花墩。

concatMap 還有第二個參數(shù)是一個 selector callback悬秉,這個 callback 會傳入四個參數(shù),分別是

  1. 外部 observable 送出的元素
  2. 內(nèi)部 observable 送出的元素
  3. 外部 observable 送出元素的 index
  4. 內(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'); }
});

JSBin | JSFiddle

這個范例的外部 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'); }
});

JSBin | JSFiddle

如果我們快速的連續(xù)點擊五下剂桥,可以在開發(fā)者工具的 network 看到每個 request 會在點擊時發(fā)送忠烛,如下圖

image.png

灰色是瀏覽器原生地停頓行為,實際上灰色的一開始就是 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'); }
});

JSBin | JSFiddle

如果我們快速的連續(xù)點擊五下,大家可以在開發(fā)者工具的 network 看到每個 request 會在點擊時發(fā)送并且會 log 出五個物件量蕊,如下圖

image.png

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'); }
});

JSBin | JSFiddle

這裡我們傳入 3 就能限制韭赘,HTTP request 最多只能同時送出 3 個,并且要等其中一個完成在處理下一個势就,如下圖

image.png

大家可以注意看上面這張圖泉瞻,我連續(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é)梆暮。

不知道今天讀者有沒有收穫呢服协? 如果有任何問題,歡迎留言給我啦粹,謝謝

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蚯涮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子卖陵,更是在濱河造成了極大的恐慌遭顶,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,681評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件泪蔫,死亡現(xiàn)場離奇詭異棒旗,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)撩荣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評論 3 399
  • 文/潘曉璐 我一進(jìn)店門铣揉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人餐曹,你說我怎么就攤上這事逛拱。” “怎么了台猴?”我有些...
    開封第一講書人閱讀 169,421評論 0 362
  • 文/不壞的土叔 我叫張陵朽合,是天一觀的道長。 經(jīng)常有香客問我饱狂,道長曹步,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,114評論 1 300
  • 正文 為了忘掉前任休讳,我火速辦了婚禮讲婚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘俊柔。我一直安慰自己筹麸,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 69,116評論 6 398
  • 文/花漫 我一把揭開白布雏婶。 她就那樣靜靜地躺著物赶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪尚骄。 梳的紋絲不亂的頭發(fā)上块差,一...
    開封第一講書人閱讀 52,713評論 1 312
  • 那天侵续,我揣著相機(jī)與錄音倔丈,去河邊找鬼憨闰。 笑死,一個胖子當(dāng)著我的面吹牛需五,可吹牛的內(nèi)容都是我干的鹉动。 我是一名探鬼主播,決...
    沈念sama閱讀 41,170評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼宏邮,長吁一口氣:“原來是場噩夢啊……” “哼泽示!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蜜氨,我...
    開封第一講書人閱讀 40,116評論 0 277
  • 序言:老撾萬榮一對情侶失蹤械筛,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后飒炎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體埋哟,經(jīng)...
    沈念sama閱讀 46,651評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,714評論 3 342
  • 正文 我和宋清朗相戀三年郎汪,在試婚紗的時候發(fā)現(xiàn)自己被綠了赤赊。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,865評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡煞赢,死狀恐怖抛计,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情照筑,我是刑警寧澤蟆炊,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布遥缕,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏诗祸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,211評論 3 336
  • 文/蒙蒙 一蝇完、第九天 我趴在偏房一處隱蔽的房頂上張望洒敏。 院中可真熱鬧,春花似錦趴生、人聲如沸阀趴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽刘急。三九已至,卻和暖如春浸踩,著一層夾襖步出監(jiān)牢的瞬間叔汁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留据块,地道東北人码邻。 一個月前我還...
    沈念sama閱讀 49,299評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像另假,于是被迫代替她去往敵國和親像屋。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,870評論 2 361

推薦閱讀更多精彩內(nèi)容