RxJS 教程

"Reactive Programming是神馬?"

互聯網上有不少不是很友好的解釋。維基百科 寬泛而玄乎。 Stackoverflow教科書式的解釋很是不適合信任Reactive Manifesto 聽起來像是給給項目經理或者是銷售的彙報。 微軟的 Rx 定義 "Rx = Observables + LINQ + Schedulers" 過重而且太微軟化了,讓人看起來不知所云。「響應」、「變化發生」這些術語沒法很好地闡釋Reactive Programming的顯著特色,聽起來和你熟悉的MV*、編程語言差異不大。 固然,個人視角也是基於模型和變換的,要是脫離了這些概念,一切都是無稽之談了。javascript

那麼我要開始吧啦吧啦了,(後文中,將使用RP代替Reactive Programming,私底下譯者將Reactive Programming,翻譯爲響應式編程)。java

RP 是針對異步數據流的編程。react

必定程度而言,RP並不算新的概念。Event Bus、點擊事件都是異步流。開發者能夠觀測這些異步流,並調用特定的邏輯對它們進行處理。使用Reactive如同開掛:你能夠建立點擊、懸停之類的任意流。一般流廉價(點擊一下就出來一個)而無處不在,種類豐富多樣:變量,用戶輸入,屬性,緩存,數據結構等等均可以產生流。舉例來講:微博迴文(譯者注:好比你關注的微博更新了)和點擊事件都是流:你能夠監聽流並調用特定的邏輯對它們進行處理。jquery

基於流的概念,RP賦予了你一系列神奇的函數工具集,使用他們能夠合併、建立、過濾這些流。 一個流或者一系列流能夠做爲另外一個流的輸入。你能夠 合併 兩個流,從一堆流中 過濾 你真正感興趣的那一些,將值從一個流 映射 到另外一個流。git

若是流是RP的核心,咱們不妨從「點擊頁面中的按鈕」這個熟悉的場景詳細地瞭解它。github

Click event stream

流是包含了有時序,正在進行事件的序列,能夠發射(emmit)值(某種類型)、錯誤、完成信號。流在包含按鈕的瀏覽器窗口被關閉時發出完成信號。web

咱們異步地捕獲發射的事件,定義一系列函數在值被髮射後,在錯誤被髮射後,在完成信號被髮射後執行。有時,咱們忽略對錯誤,完成信號地處理,僅僅關注對值的處理。對流進行監聽,一般稱爲訂閱,處理流的函數是觀測者,流是被觀測的主體。這就是觀測者設計模式ajax

教程中,咱們有時會使用ASCII字符來繪製圖表:編程

--a---b-c---d---X---|->

a, b, c, d 是數據流發射的值
X 是數據流發射的錯誤
| 是完成信號
---> 是時序軸

嗶嗶完了,咱們來點新的,否則很快你就感受到寂寞了。咱們將把原來的點擊事件流轉換爲新的點擊事件流。json

首先咱們建立一個計數流來代表按鈕被點擊的次數。在RP中,每個流都擁有一系列方法,例如mapfilterscan 等等。當你在流上調用這些方法,例如clickStream.map(f),會返回基於點擊事件流的新的流,同時原來的點擊事件流並不會被改變,這個特性被稱爲不可變性(immutability)。不可變性與RP配合相得益彰,如同美酒加咖啡。咱們能夠鏈式地調用他們:clickStream.map(f).scan(g)

clickStream: ---c----c--c----c------c--->
               vvvvv map(c becomes 1) vvvvv
               ---1----1--1----1------1--->
               vvvvvvvvv  scan(+) vvvvvvvvv
counterStream: ---1----2--3----4------5--->

map(f) 函數對原來的流使用咱們出入的f函數進行轉換,並生成新的流。在上面的例子中,咱們將每一次點擊映射爲數字1。scan(g)函數將全部流產生的值進行彙總,經過傳入x = g(accumulated, current)函數產生新的值,g 是簡單的求和函數。最後 counterStream在點擊發生後發射點擊事件發生的總數。

爲了展現Reactive的真正力量,咱們舉個例子:你想要「兩次點擊」事件的流,或者是「三次點擊」,或者是n次點擊的流。深呼吸一下,試着想一想怎麼用傳統的命令、狀態式方法來解決。我打賭這個這會至關操蛋,你會搞些變量來記錄狀態,還要搞些處理時延的機制。

若是用RP來解決,太他媽簡單了。實際上4行代碼就能夠搞定。先不要看代碼,無論你是菜鳥仍是牛逼,使用圖表來思考可使你更好地理解構建這些流的方法。

Multiple clicks stream

灰色框裏面的函數會把一個流轉換成另一個流。首先咱們把點擊打包到list中,若是點擊後消停了250毫秒,咱們就從新打包一個新的list(顯然buffer(stream.throttle(250ms))就是用來幹這個的,不明白細節沒有關係,反正是demo嘛)。咱們在列表上調用map(),將列表的長度映射爲一個整數的流。最後,咱們經過filter(x >= 2)過濾掉整數1。哈哈:3個操做就生成了咱們須要的流,如今咱們能夠訂閱(監聽)這個流,而後來完成咱們須要的邏輯了。

經過這個例子,我但願你能感覺到使用RP的牛逼之處了。這僅僅是冰山一角。你能夠在不一樣地流上(好比API響應的流)進行一樣的操做。同時,Reactive還提供了許多其餘實用的函數。

"我要在從此採用RP範式進行編程嗎?"

RP 提升了編碼的抽象程度,你能夠更好地關注在商業邏輯中各類事件的聯繫避免大量細節而瑣碎的實現,使得編碼更加簡潔。

使用RP,將使得數據、交互錯綜複雜的web、移動app開發收益更多。10年之前,與網頁的交互僅僅是提交表單、而後根據服務器簡單地渲染返回結果這些事情。App進化得愈來愈有實時性:修改表單中一個域能夠同步地更新到後端服務器。「點贊」信息實時地在不一樣用戶設備上同步。

現代App中大量的實時事件創造了更好的交互和用戶體驗,披荊斬棘須要利劍在手,RP就是你手中的利劍。

經過實例RP編程思想

咱們將從實例能夠深刻RP的編程思想,文章末尾,一個完整地實例應用會被構建,你也會理解整個過程。

我選擇 JavaScriptRxJS 做爲實例的構建工具。由於大多開發者都熟悉JavaScript語言。Rx* library family 在各類語言和平臺都是實現 (.NET, Java, Scala, Clojure, JavaScript, Ruby, Python, C++, Objective-C/Cocoa, Groovy, 等等)。不管你選擇在哪一個平臺或者那種語言實踐RP,你都將從本教程中受益。(譯者注:Rx,即ReactiveX,其中X表明不一樣的語言和技術棧,好比.NET,Java,Scala,Ruby,Javascript。RxJS表示RP基於Javascript語言的實現。後文中Rx表明全部實現了RP的特定技術棧)

微博(Twitter)簡易版「你可能感興趣的人」

微博主頁,有一個組件會推薦給你那些你可能感興趣的人。

Twitter Who to follow suggestions box

咱們的Demo將使用這個場景,關注下面這些主要特性:

  • 頁面打開後,經過API加載數據展現3個你可能感興趣的用戶帳號

  • 點擊「刷新」按鈕,從新加載三個新的用戶帳號

  • 在一個用戶帳號上點擊'x' 按鈕,清除當前這個帳戶,從新加載一個新的帳戶

  • 每行展現帳戶的信息和這個帳戶主頁的連接

其餘特性和按鈕咱們暫且忽略,因爲Twitter在最近關閉了公共API受權接口,咱們選擇Github做爲代替,展現GitHub用戶的帳戶。實例中咱們使用該接口獲取GitHub用戶.

若是你但願先睹爲快,完成後的代碼已經發布在了Jsfiddle

"你可能感興趣的用戶"請求&響應

這個問題使用Rx怎麼解?,呵呵,咱們從Rx的箴言開始: 神馬都是流 。首先咱們作最簡單的部分——頁面打開後經過API加載3個帳戶的信息。分三步走:(1)發一個請求(2)得到響應(3)依據響應渲染頁面。那麼,咱們先使用流來表示請求。我靠,表示個請求用得着嗎?不過千里之行始於足下。

頁面加載時,僅須要一個請求。因此這個數據流只包含一個簡單的反射值。稍後,咱們再研究如何多個請求出現的狀況,如今先從一個請求開始。

--a------|->

a是字符串 'https://api.github.com/users'

這個流中包含了咱們但願請求的URL地址。一旦這個請求事件發生,咱們能夠獲知兩件事情:請求流發射值(字符串URL)的時間就是請求須要被執行的時間,請求須要請求的地址就是請求流發射的值。

在Rx*中構建一個單值的流很容易。官方術語中把流稱爲「觀察的對象」("Observable"),由於流能夠被觀察、訂閱,這麼稱呼顯得很蠢,我本身把他們稱爲 stream

var requestStream = Rx.Observable.just('https://api.github.com/users');

目前這個攜帶字符串的流沒有其餘操做,咱們須要在這個流發射值以後,作點什麼:經過訂閱 這個流來實現。

requestStream.subscribe(function(requestUrl) {
  // 執行異步請求
  jQuery.getJSON(requestUrl, function(responseData) {
    // ...
  });
}

咱們採用了jQuery的Ajax回調 (假設讀着已經瞭解jQuery ajax回調) 來處理異步請求操做。 且慢,Rx天生就是處理異步 數據流的,
爲什麼不把請求的響應做爲一個攜帶數據的流呢? 麼麼噠,概念上沒有問題,咱們就來操做一下。

requestStream.subscribe(function(requestUrl) {
  // 執行異步請求
  var responseStream = Rx.Observable.create(function (observer) {
    jQuery.getJSON(requestUrl)
    .done(function(response) { observer.onNext(response); })
    .fail(function(jqXHR, status, error) { observer.onError(error); })
    .always(function() { observer.onCompleted(); });
  });
  
  responseStream.subscribe(function(response) {
    // 業務邏輯
  });
}

使用Rx.Observable.create()方法能夠自定義你須要的流。你須要明確通知觀察者(或者訂閱者)數據流的到達(onNext()) 或者錯誤的發生(onError())。這個實現中,咱們封裝了jQuery 的異步 Promise。那麼Promise也是可觀察對象嗎?

funny face

冰狗,你猜對啦!

可觀察對象(Observable)是超級Promise(原文Promise++,能夠對比C,C++,C++在兼容C的同時引入了面向對象等特性)。 在Rx環境中,你能夠簡單的經過var stream = Rx.Observable.fromPromise(promise)將Promise轉換爲可觀察對象, 咱們後面將這樣使用, 惟一的區別是,可觀察對象與Promises/A+ 並不兼容, 可是理論上不會產生衝突。 Promise 能夠看作只能發射單值的可觀察對象,Rx流則容許返回多個值。

不過,可觀察對象至少和Promise同樣強大。若是你相信針對Promise的那些吹捧,不妨也留意一下Rx環境中的可觀察對象。

回到咱們的例子,細心的你確定看到了subscribe()的嵌套使用,這和回調函數嵌套同樣使人惱火。responseStream 的確和 requestStream 存在依賴關係。前面咱們不是提到過Rx有一些牛逼的工具集嗎?在Rx中咱們擁有簡單的機制把一個流轉化爲一個新的流,咱們不妨試試。

咱們先介紹 map(f)函數。該函數在流A的每一個之上調用函數f() , 而後在流B上生成對應的新值。若是在請求、響應流上調用map(f),咱們能夠將請求的URL隱射爲響應流中的Promise(此時響應流中包含了Promise的序列)。

var responseMetastream = requestStream
  .map(function(requestUrl) {
    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
  });

咱們把上面代碼執行後的返回結果稱爲 metastream (譯者注:按字面能夠翻譯爲「元流」,即包含流的流。相似概念例如:元編程——用於生成程序的編程方法;元知識——獲取知識的知識):包含其餘流的流。沒什麼嚇人的, 一個metastream會在執行後發射一個流。 你能夠把它看作一個指針 指針): 每個發射的值是指向另一個流的 指針 。在咱們的例子中,每個URL被映射爲一個指向Promise流的指針,每個Promise流中包含了相應的響應信息。

Response metastream

(譯者注:如下給出 metastream 的方法的解析方法,方便與下面的方法進行對比):

responseMetastream.subscribe(function(streamedPromise) {
    // 首先展開metastream,獲取內部的流
    streamedPromise.subscribe(function(responseJsonObject) {
        // 返回內部流發射的值
        return responseJsonObject;
    });
});

當前版本響應產生的metastream看起來有些讓人疑惑,彷佛用處不大。當前場景中,咱們僅僅須要得到簡單的響應流,流中發射的值爲簡單的JSON對象。使用flatMap:這個函數能夠將枝幹的流的值發射到主幹流之上。固然metastream的產生並非bug,只是這個場景不適合而已,map()flatMap()都是Rx處理異步請求工具中的一部分。(譯者注:若是流A中包含了若干其餘流,在流A上調用flatMap()函數,將會發射其餘流的值,並將發射的全部值組合生成新的流。)

var responseStream = requestStream
  .flatMap(function(requestUrl) {
    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
  });

Response stream

贊!響應流是依照請求流定義的,若是 場景中生成了更多的請求流,咱們也會生成一樣多的響應流:

請求流:  --a-----b--c------------|->
響應流:  -----A--------B-----C---|->

(小寫字母表示請求, 大寫字母表明響應)

得到響應流以後,咱們就能夠再訂閱後渲染頁面了:

responseStream.subscribe(function(response) {
  // 在瀏覽器中渲染響應數據的邏輯
});

馬克一下目前的代碼:

var requestStream = Rx.Observable.just('https://api.github.com/users');

var responseStream = requestStream
  .flatMap(function(requestUrl) {
    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
  });

responseStream.subscribe(function(response) {
  // 在瀏覽器中渲染響應數據的邏輯
});

刷新「你可能感興趣的用戶」

忘了說了,咱們每一次請求都會返回100個GitHub用戶的數據。GitHub的API只容許咱們設置頁面的偏移量可是不能設置每次得到數據的數量。嗯,咱們須要3個推薦用戶的數據,其餘97個就這樣浪費了。暫時忽略這個問題,後面咱們看看怎麼緩存數據來減小數據的浪費。

每一次點擊刷新按鈕(高能注意:是一個按鈕,點擊後刷新「我可能感興趣的人」的數據,而不是瀏覽器的刷新按鈕),請求流都會發射新的URL值,咱們以此得到新的響應。刷新分爲兩步:產生一個刷新按鈕被點擊的事件流(RP箴言:神馬都是流);訂閱刷新事件流後改變請求流的URL地址。RxJS提供了工具方便咱們將時間監聽器轉換爲可觀察對象。

var refreshButton = document.querySelector('.refresh');
var refreshClickStream = Rx.Observable.fromEvent(refreshButton, 'click');

由於點擊刷新事件並不會攜帶須要請求的API的URL,咱們須要把每一次點擊映射到真正的URL之上。具體實現方式是,在刷新點擊流發生後,咱們經過產生隨機的頁面拼湊出URL,並向GitHub發起請求。

var requestStream = refreshClickStream
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return 'https://api.github.com/users?since=' + randomOffset;
  });

因爲是簡單的教程,我並無寫相關的測試,可是我仍然知道原先的功能被我搞砸啦。呃。。。頁面打開後竟然沒有請求流了,除非我點擊刷新按鈕,不然數據怎麼都出不來。擦。。。我但願 無論 是點擊刷新按鈕"_仍是_"第一次打開頁面,均可以產生得到「我可能感興趣的人」的數據的GitHub的請求流。

把兩個流分開寫特別簡單,咱們已經知道怎麼作了:

var requestOnRefreshStream = refreshClickStream
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return 'https://api.github.com/users?since=' + randomOffset;
  });
  
var startupRequestStream = Rx.Observable.just('https://api.github.com/users');

可是咱們怎麼把兩個流「合併」在一塊呢?使用 merge()函數吧。咱們用ASCII圖表來解釋這個函數的做用:

流 A: ---a--------e-----o----->
流 B: -----B---C-----D-------->
          vvvvvvvvv merge vvvvvvvvv
          ---a-B---C--e--D--o----->

使用merge()後簡單多了:

var requestOnRefreshStream = refreshClickStream
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return 'https://api.github.com/users?since=' + randomOffset;
  });
  
var startupRequestStream = Rx.Observable.just('https://api.github.com/users');

var requestStream = Rx.Observable.merge(
  requestOnRefreshStream, startupRequestStream
);

若是不須要requestOnRefreshStream、startupRequestStream這兩個中間流,寫法更乾淨、簡潔。

var requestStream = refreshClickStream
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return 'https://api.github.com/users?since=' + randomOffset;
  })
  .merge(Rx.Observable.just('https://api.github.com/users'));

還能更簡單,更有可讀性:

var requestStream = refreshClickStream
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return 'https://api.github.com/users?since=' + randomOffset;
  })
  .startWith('https://api.github.com/users');

startWith() 函數的做用和它的命名同樣。 不管是什麼樣的流,startWith(x) 都會把x做爲這個流的啓示輸入併發射出來。 上面的實現,還不夠DRY(Don't repeat yourself,不要重複!),API請求的URL地址重複了兩遍。咱們將 startWith() 緊接在refreshClickStream以後,在頁面打開後就模擬一次點擊。

var requestStream = refreshClickStream.startWith('startup click')
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return 'https://api.github.com/users?since=' + randomOffset;
  });

Nice!事情不會被搞砸了,startWith()完美解決了問題。

3位「你可能感興趣的用戶」的流的構建

目前爲止,僅僅在訂閱(subscribe())時,你會觸及到「感興趣的用戶」區塊的渲染。可是經過刷新按鈕,問題接踵而至:你點擊了刷新按鈕,在新的響應到達以前,原來的「你可能感興趣的」3個用戶並不會立刻消失。爲了加強用戶體驗,咱們但願在用戶點擊了刷新按鈕後就清楚老數據。

refreshClickStream.subscribe(function() {
  // 清楚舊數據: 3個你可能感興趣的用戶的DOM元素
});

停!不要用力過猛。兩個 訂閱行爲都會影響到這個區塊的渲染。(responseStream.subscribe()refreshClickStream.subscribe()),而且上面的設計也不符合關注分離的理念。還記得RP 神馬都是流 的箴言嗎?

Mantra

那麼開始構建這個專門的推薦流:流會發射「你可能感興趣的用戶」的JSON對象。咱們會分別構建三種這樣的流,第一種長這個樣:

var suggestion1Stream = responseStream
  .map(function(listUsers) {
    // 隨機從列表中取出一個用戶
    return listUsers[Math.floor(Math.random()*listUsers.length)];
  });

另外兩個流suggestion2Streamsuggestion3Stream複製粘貼就好啦。呃。。。DRY不要重複,我把這個問題做爲這個教程的聯繫,本身作一遍你會去思考這類場景中如何避免代碼的重複。

譯者注:若是使用UnderScore,一種方法是,新的方法老是會返回JSON Object數組:

var suggestionStream = responseStream
  .map(suggestionN(listUsers, n));

function suggestionN(listUsers, n) {
    _.times(n, function() {
        return listUsers[Math.floor(Math.random()*listUsers.length)];
    })
}

咱們再也不訂閱響應流,而是變動爲:

suggestion1Stream.subscribe(function(suggestion) {
  // 在區塊中渲染1位用戶的DOM元素
});

回到原始需求:「每一次刷新後,清除原來的用戶」,咱們能夠在刷新後,返回null做爲推薦流:

var suggestion1Stream = responseStream
  .map(function(listUsers) {
    // 隨機從列表中取出一個用戶
    return listUsers[Math.floor(Math.random()*listUsers.length)];
  })
  .merge(
    refreshClickStream.map(function(){ return null; })
  );

在渲染環節,null表明無數據,咱們就隱藏以前的DOM元素。

suggestion1Stream.subscribe(function(suggestion) {
  if (suggestion === null) {
    // 在區塊中隱藏一個推薦用戶的DOM元素
  }
  else {
    // 在區塊中渲染一個推薦用戶的DOM元素
  }
});

整個事件流如圖所示:

刷新按鈕流: ----------o--------o---->
     請求流: -r--------r--------r---->
     響應流: ----R---------R------R-->   
 推薦1個用戶: ----s-----N---s----N-s-->

N 表示 null.

頁面打開後,咱們渲染「空」推薦區塊,能夠經過在推薦流中附加startWith(null)實現:

var suggestion1Stream = responseStream
  .map(function(listUsers) {
    // 隨機從列表中取出一個用戶
    return listUsers[Math.floor(Math.random()*listUsers.length)];
  })
  .merge(
    refreshClickStream.map(function(){ return null; })
  )
  .startWith(null);

Which results in:

刷新按鈕流: ----------o---------o---->
      請求流: -r--------r---------r---->
      響應流: ----R----------R------R-->   
 推薦1個用戶:  -N--s-----N----s----N-s-->

關閉一個推薦元素,從緩存得到新的推薦元素

最後一個須要實現的功能是:點擊'x'按鈕後關閉當前的推薦元素,載入一個新的數據並渲染。拍腦殼意向,不管點擊了啥按鈕,咱們從新請求一次新數據,生成一個新的響應流就行了:

var close1Button = document.querySelector('.close1');
var close1ClickStream = Rx.Observable.fromEvent(close1Button, 'click');
// close2Button 和 close3Button 做爲練習

var requestStream = refreshClickStream.startWith('startup click')
  .merge(close1ClickStream) // 加上這個
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return 'https://api.github.com/users?since=' + randomOffset;
  });

擦,點擊了關閉按鈕整個推薦區塊都被刷新了!看來咱們只有使用原來的相應流才能解決這個bug,何況每次慷慨大方的GitHub給咱們100個用戶的數據,咱們只使用3個,還有1大堆留着等咱們用呢,沒有必要再請求更多的數據了。

讓咱們從流的角度思考,當點擊'x'事件發生後,咱們使用 最近一次的相應流 並從中隨機取出用戶就行了:

請求流: --r--------------->
      響應流: ------R----------->
   點擊關閉流: ------------c----->
推薦1個用戶流: ------s-----s----->

在Rx*框架中,一個使用函數叫 combineLatest 。 函數將兩個流做爲輸入,而且當其中任意一個流發射以後, combineLatest 都會組合兩個流中最新的值 ab而後輸出一個新的流,流的值爲 c = f(x,y) 其中 f(x, y) 是傳入的自定義函數,配合上時序圖更好理解:

流 A:     --a-----------e--------i-------->
流 B:     -----b----c--------d-------q---->
          vvvvvvvv combineLatest(f) vvvvvvv
          ----AB---AC--EC---ED--ID--IQ---->

這裏的函數f,將輸入的字符串變爲大寫

如今咱們在 close1ClickStreamresponseStream使用combineLatest() , 只要用戶點擊關閉按鈕,咱們就結合最新的響應流來產生suggestion1Stream。 另外一個方面,combineLatest() 是一個同步操做:每當新的響應流發射了值, 一樣會結合 close1ClickStream產生新的推薦數據。這樣咱們大大簡化了suggestion1Stream

var suggestion1Stream = close1ClickStream
  .combineLatest(responseStream,             
    function(click, listUsers) {
      return listUsers[Math.floor(Math.random()*listUsers.length)];
    }
  )
  .merge(
    refreshClickStream.map(function(){ return null; })
  )
  .startWith(null);

最後還有一點點問題:combineLatest()須要結合傳入的兩個流,若是其中一個流從未發射過任何值,combineLatest()將不會輸入任何新的流。回顧一下上面的ASCII圖表,當第一個流發射值a時,不會有任何輸出,僅當第二個流也發射了值b後,combineLatest()纔會開始向外輸出。

解決方法不少,咱們採起最簡單的方式(上面例子也用到過),咱們在頁面打開時限模擬一次關閉按鈕的點擊:

var suggestion1Stream = close1ClickStream.startWith('startup click') // we added this
  .combineLatest(responseStream,             
    function(click, listUsers) {
      return listUsers[Math.floor(Math.random()*listUsers.length)];
    }
  )
  .merge(
    refreshClickStream.map(function(){ return null; })
  )
  .startWith(null);

總結

再Mark一下當前的代碼,是否是頗有成就感:

var refreshButton = document.querySelector('.refresh');
var refreshClickStream = Rx.Observable.fromEvent(refreshButton, 'click');

var closeButton1 = document.querySelector('.close1');
var close1ClickStream = Rx.Observable.fromEvent(closeButton1, 'click');
// close2 和 close3 做爲練習

var requestStream = refreshClickStream.startWith('startup click')
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return 'https://api.github.com/users?since=' + randomOffset;
  });

var responseStream = requestStream
  .flatMap(function (requestUrl) {
    return Rx.Observable.fromPromise($.ajax({url: requestUrl}));
  });

var suggestion1Stream = close1ClickStream.startWith('startup click')
  .combineLatest(responseStream,             
    function(click, listUsers) {
      return listUsers[Math.floor(Math.random()*listUsers.length)];
    }
  )
  .merge(
    refreshClickStream.map(function(){ return null; })
  )
  .startWith(null);
// suggestion2Stream 和 suggestion3Stream 做爲練習

suggestion1Stream.subscribe(function(suggestion) {
  if (suggestion === null) {
    // 隱藏一個用戶的DOM元素
  }
  else {
    // 渲染一個新的推薦用戶的DOM元素
  }
});
相關文章
相關標籤/搜索