{"componentChunkName":"component---src-templates-blog-post-js","path":"/2020-05-28/","result":{"data":{"site":{"siteMetadata":{"title":"Howard's Note","disqusShortname":""}},"markdownRemark":{"id":"20d68e5f-ab22-598a-b7a2-ea1f88a0e2a4","html":"<h2>Observable 與 pip</h2>\n<p>在 Rxjs中，<code class=\"language-text\">Observable</code> 是一個可供訂閱的物件。當一個 <code class=\"language-text\">Observable</code> 被 <code class=\"language-text\">subscribe</code> （訂閱）後，直到這個 <code class=\"language-text\">Observable</code> 發出 complete 之前，訂閱這個 Observable的 <code class=\"language-text\">Observer</code> 都會收到 <code class=\"language-text\">Observable</code> 發出的值。</p>\n<p>因為 <code class=\"language-text\">Observable</code> 會在特定的條件下，不斷的發出值，因此就會形成一個資料流。可以把資料流想像成水流，而 發出值的 <code class=\"language-text\">Observable</code> 就是水的源頭。</p>\n<p>而當資料流產生後，我們可能還會對資料的值一些處理，最將處理後的值發給訂閱這個 <code class=\"language-text\">Observable</code> 的 <code class=\"language-text\">Observer</code>。這時就可以使用 <code class=\"language-text\">pipe</code>。可以把 <code class=\"language-text\">pipel</code> 想像成一個水管。</p>\n<p>打個比方，源 <code class=\"language-text\">Observable</code> 就像 水龍頭，而 <code class=\"language-text\">pipe</code> 像蓮蓬頭。當水龍頭打開後，我們（<code class=\"language-text\">Observer</code>）最後看到的水，會是蓮蓬頭導出的水。</p>\n<h3>pipe 中的 operator （運算子），input 和 output都會是 Observable</h3>\n<p>所有的運算子，其實都是一個<code class=\"language-text\">Observable</code>。以 <code class=\"language-text\">map</code>為例，當project return 值後，map 會發出project return 的值；pipe中的函式會像接水管一樣，下一個Operator訂閱上一個Operator(Observable)。</p>\n<p>訂閱的動作，可以透過 Subscriber 完成。它是一個內部使用的類別，實現了 Observer界面，並且繼承 Subscription。內部的 Observer 界面，會被轉化為 Subscriber。 Subscriber 實現了「管理」及「取消」多個Observable 訂閱的能力 。因此，在 pipe中，當 其中的某個運算子要結束掉整個 pipe的時候，可以通過 Subscriber 的 unsubscribe() 方法，取消整個管線的訂閱。</p>\n<p>引用官方說明：</p>\n<blockquote>\n<p>A Pipeable Operator is a function that takes an Observable as its input and returns another Observable. It is a pure operation: the previous Observable stays unmodified.</p>\n</blockquote>\n<h2>map operator</h2>\n<p>函式： <code class=\"language-text\">map(project: Function, thisArg: any): Observable</code></p>\n<p>map operator是很好懂的一個運算子。它的 <code class=\"language-text\">project</code> function 會接受上一個運算子發出的值，並且經過處理後，回傳處理後的值。</p>\n<p>範例：</p>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token comment\">// RxJS v6+</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> from <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'rxjs'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> map <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'rxjs/operators'</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// 发出 (1,2,3,4,5)</span>\n<span class=\"token keyword\">const</span> source <span class=\"token operator\">=</span> <span class=\"token function\">from</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token number\">1</span><span class=\"token punctuation\">,</span> <span class=\"token number\">2</span><span class=\"token punctuation\">,</span> <span class=\"token number\">3</span><span class=\"token punctuation\">,</span> <span class=\"token number\">4</span><span class=\"token punctuation\">,</span> <span class=\"token number\">5</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token comment\">// 每个数字加10</span>\n<span class=\"token keyword\">const</span> example <span class=\"token operator\">=</span> source<span class=\"token punctuation\">.</span><span class=\"token function\">pipe</span><span class=\"token punctuation\">(</span><span class=\"token function\">map</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">val</span> <span class=\"token operator\">=></span> val <span class=\"token operator\">+</span> <span class=\"token number\">10</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token comment\">// 输出: 11,12,13,14,15</span>\n<span class=\"token keyword\">const</span> subscribe <span class=\"token operator\">=</span> example<span class=\"token punctuation\">.</span><span class=\"token function\">subscribe</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">val</span> <span class=\"token operator\">=></span> console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span>val<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<h2>mergeMap and switchMap</h2>\n<p>mergeMap: <code class=\"language-text\">mergeMap(project: function: Observable, resultSelector: function: any, concurrent: number): Observable</code></p>\n<p>switchMap: <code class=\"language-text\">switchMap(project: function: Observable, resultSelector: function(outerValue, innerValue, outerIndex, innerIndex): any): Observable</code></p>\n<p>很多時候，我們會需要回傳一個 <code class=\"language-text\">Observable</code>，並且自已處理 <code class=\"language-text\">Observable</code> 的發出值。最常見的範例就是在接到值後，面發起一個 http請求。此時，return的值是一個 <code class=\"language-text\">Observable</code>。</p>\n<p><code class=\"language-text\">mergeMap</code> 跟 <code class=\"language-text\">switchMap</code> 事實上也沒有什麼魔法。它就是 <code class=\"language-text\">project</code> function 直接回傳一個 Observable，而下一個運算子取得的值，是由這個回傳的Observable發出的。</p>\n<blockquote>\n<p><code class=\"language-text\">mergeMap</code>、<code class=\"language-text\">switchMap</code> 跟 <code class=\"language-text\">map</code> 的差別，在於 <code class=\"language-text\">project</code> function。 map回傳的是Observable內的值；mergeMap/ switchMap回傳的是一個函式。</p>\n</blockquote>\n<h3>怎麼知道發出的值是從哪一個 Observable發出的？Observable何時 complete</h3>\n<p>這就是 <code class=\"language-text\">mregeMap</code> 和 <code class=\"language-text\">switchMap</code> 的不同處了。因為它們的<code class=\"language-text\">project</code> function 回傳值是 <code class=\"language-text\">Observable</code>，我們不會知道它會什麼時候發出值、什麼時候結束。</p>\n<p><code class=\"language-text\">mergeMap</code> 跟 <code class=\"language-text\">switchMap</code> 都有內部的 Observable，下一個運算子接收的值，是由這個 Observable 發出的值。而兩者的差別只在，使用<code class=\"language-text\">switchMap</code>的時候，當新的observable進來了，上一個 <code class=\"language-text\">Observable</code>會直接 <code class=\"language-text\">complete</code>。而 <code class=\"language-text\">mergeMap</code>不會<code class=\"language-text\">complete</code>上一個 Observable。</p>\n<p>當Observable被發出後，該Observable之後再發出的任何訊息，都不會再被訂閱者接收（在pipe中就是下一個運算子）。</p>\n<p><code class=\"language-text\">switchMap</code>一次只會有一個訂閱中的observable。而<code class=\"language-text\">mergeMap</code>會一次管理多個<code class=\"language-text\">Observable</code>訂閱。</p>\n<p>因此，使用switchMap可以防止潛在可能memory leak的問題。大多數的時候，使用<code class=\"language-text\">switchMap</code>是比較安全的。然而，有些情況下，可能還是適合使用 <code class=\"language-text\">mergeMap</code>。這就要看使用情境跟經驗了。</p>\n<h2>例子</h2>\n<p>這是從rxjs官網看到，覺得比較有趣的例子。</p>\n<p><a href=\"https://stackblitz.com/edit/typescript-ivdebg?file=index.ts\">StackBlitz</a></p>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token comment\">// RxJS v6+</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> interval<span class=\"token punctuation\">,</span> fromEvent<span class=\"token punctuation\">,</span> merge<span class=\"token punctuation\">,</span> empty <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'rxjs'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> switchMap<span class=\"token punctuation\">,</span> scan<span class=\"token punctuation\">,</span> takeWhile<span class=\"token punctuation\">,</span> startWith<span class=\"token punctuation\">,</span> mapTo <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'rxjs/operators'</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">const</span> countdownSeconds <span class=\"token operator\">=</span> <span class=\"token number\">10</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> <span class=\"token function-variable function\">setHTML</span> <span class=\"token operator\">=</span> <span class=\"token parameter\">id</span> <span class=\"token operator\">=></span> <span class=\"token parameter\">val</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">(</span>document<span class=\"token punctuation\">.</span><span class=\"token function\">getElementById</span><span class=\"token punctuation\">(</span>id<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>innerHTML <span class=\"token operator\">=</span> val<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> pauseButton <span class=\"token operator\">=</span> document<span class=\"token punctuation\">.</span><span class=\"token function\">getElementById</span><span class=\"token punctuation\">(</span><span class=\"token string\">'pause'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> resumeButton <span class=\"token operator\">=</span> document<span class=\"token punctuation\">.</span><span class=\"token function\">getElementById</span><span class=\"token punctuation\">(</span><span class=\"token string\">'resume'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> interval$ <span class=\"token operator\">=</span> <span class=\"token function\">interval</span><span class=\"token punctuation\">(</span><span class=\"token number\">1000</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">pipe</span><span class=\"token punctuation\">(</span><span class=\"token function\">mapTo</span><span class=\"token punctuation\">(</span><span class=\"token operator\">-</span><span class=\"token number\">1</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">const</span> pause$ <span class=\"token operator\">=</span> <span class=\"token function\">fromEvent</span><span class=\"token punctuation\">(</span>pauseButton<span class=\"token punctuation\">,</span> <span class=\"token string\">'click'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">pipe</span><span class=\"token punctuation\">(</span><span class=\"token function\">mapTo</span><span class=\"token punctuation\">(</span><span class=\"token boolean\">false</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> resume$ <span class=\"token operator\">=</span> <span class=\"token function\">fromEvent</span><span class=\"token punctuation\">(</span>resumeButton<span class=\"token punctuation\">,</span> <span class=\"token string\">'click'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">pipe</span><span class=\"token punctuation\">(</span><span class=\"token function\">mapTo</span><span class=\"token punctuation\">(</span><span class=\"token boolean\">true</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">const</span> timer$ <span class=\"token operator\">=</span> <span class=\"token function\">merge</span><span class=\"token punctuation\">(</span>pause$<span class=\"token punctuation\">,</span> resume$<span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">.</span><span class=\"token function\">pipe</span><span class=\"token punctuation\">(</span>\n    <span class=\"token function\">startWith</span><span class=\"token punctuation\">(</span><span class=\"token boolean\">true</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    <span class=\"token function\">switchMap</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">val</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">(</span>val <span class=\"token operator\">?</span> interval$ <span class=\"token operator\">:</span> <span class=\"token function\">empty</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    <span class=\"token function\">tap</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">val</span> <span class=\"token operator\">=></span> console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span>val<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    <span class=\"token function\">scan</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">acc<span class=\"token punctuation\">,</span> curr</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">(</span>curr <span class=\"token operator\">?</span> curr <span class=\"token operator\">+</span> acc <span class=\"token operator\">:</span> acc<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> countdownSeconds<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    <span class=\"token function\">takeWhile</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">v</span> <span class=\"token operator\">=></span> v <span class=\"token operator\">>=</span> <span class=\"token number\">0</span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">.</span><span class=\"token function\">subscribe</span><span class=\"token punctuation\">(</span><span class=\"token function\">setHTML</span><span class=\"token punctuation\">(</span><span class=\"token string\">'remaining'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>首先，原 <code class=\"language-text\">Observable</code>是 pause和 resume兩個 event 的 merge。\n因此，timer$ 要馬會接收 pause button 的 click事件，回傳 <code class=\"language-text\">true</code>\n要馬會接收 resume button 的 click事件，回傳 <code class=\"language-text\">false</code>。</p>\n<p>接著，<code class=\"language-text\">switchMap</code> 會接收 merge 發出的 true 或 false 值，並判斷要回傳 <code class=\"language-text\">interval$</code> observable 或是 empty。</p>\n<p>而 <code class=\"language-text\">interval$</code>這個 observable會每隔一秒發出 -1 。</p>\n<p>這裡因為使用 <code class=\"language-text\">switchMap</code>，所以當收到新的 click事件，前一個 回傳的 <code class=\"language-text\">observable</code>會被 結束掉，因此不會有潛在的 memory leak風險。</p>\n<p>接下來，<code class=\"language-text\">scan</code>會將收到的值累加，並發出值給下一個運算子。</p>\n<p>最後，<code class=\"language-text\">takeWhile</code> 會在 v > 0 條件成立時，發出接收到的值。</p>\n<p>這樣整個 pipe做到的效果，就是一個 監聽 按鈕有沒有被按，並且做對應動作的倒計時器了。</p>\n<p> 在switchMap 後面 加上一個 <code class=\"language-text\">tap</code>/<code class=\"language-text\">do</code> 運算子，log一下接收的值，對<code class=\"language-text\">switchMap</code>會更了解。當你按下 pause時，switchMap會對內部的Obesrvable送出complete，並且發出一個新的Observable，在這裡是 <code class=\"language-text\">empty</code>。因此，\n前一個<code class=\"language-text\">interval$</code> 的 Obsevable因為已經complete，不會再發出值了，達成停止counting 的效果。</p>\n<h2>參考</h2>\n<p><a href=\"https://medium.com/allen%E7%9A%84%E6%8A%80%E8%A1%93%E7%AD%86%E8%A8%98/rxjs-mergeMap-%E7%AD%86%E8%A8%98-b971778cbff4\">https://medium.com/allen%E7%9A%84%E6%8A%80%E8%A1%93%E7%AD%86%E8%A8%98/rxjs-mergeMap-%E7%AD%86%E8%A8%98-b971778cbff4</a></p>\n<p><a href=\"https://rxjs-cn.github.io/learn-rxjs-operators/operators/transformation/switchMap.html\">https://rxjs-cn.github.io/learn-rxjs-operators/operators/transformation/switchMap.html</a></p>\n<p><a href=\"https://rxjs-cn.github.io/learn-rxjs-operators/operators/transformation/mergeMap.html\">https://rxjs-cn.github.io/learn-rxjs-operators/operators/transformation/mergeMap.html</a></p>","frontmatter":{"title":"RXJS運算子-mergeMap、switchMap","date":"May 28, 2020","description":"rxjs剛開始學習的一些筆記","tags":["rxjs","archive"],"template":"post"}}},"pageContext":{"slug":"/2020-05-28/","previous":{"node":{"fields":{"slug":"/2020-05-26-go-starter/"},"frontmatter":{"date":"26 May, 2020","title":"初探 Golang","tags":["go"]}}},"next":{"node":{"fields":{"slug":"/2020-05-31-rxjs-src/Rxjs/"},"frontmatter":{"date":"30 May, 2020","title":"rxjs source trace","tags":["achieve","javascript"]}}}}},"staticQueryHashes":["2841359383","3810545343","916993862"]}