JS函數式編程【譯】4.4 函數式響應式編程

函數式響應式編程

咱們再來創建另外一種類型的應用,他的工做方式差很少,都是用函數式編程來響應狀態變化。 可是這回應用不會依賴於事件監聽。 javascript

來想象一下,你在一個新聞媒體公司工做,你的老闆讓你創建一個web應用來跟蹤競選日的政府競選結果。 數據會根據地方選區的結果持續流動,因此顯示在頁面上的結果是具備響應式特徵的。然而咱們還需跟蹤每個區域的結果, 因此會有多個對象須要追蹤。 html

咱們與其創建一個巨大的面向對象的層次結構來爲接口建模,不如把它描述爲聲明式的不可變數據。 咱們能夠把它轉換爲純函數或者半純函數,半純函數在必須保持的狀態要更新時才產生反作用(理想情況下不會不少)。 java

咱們將使用Bacon.js庫,它能夠快速開發函數式響應式編程(FRP)的應用。這個應用只在選舉日使用, 咱們的老闆認爲它花費的時間應該與之成比例。經過函數式編程和像Bacon.js這樣的庫,咱們將僅須要一半的時間。 web

不過首先咱們須要一些對象來描述選區,好比州、省、區等等。ajax

function Region(name, percent, parties) {
  // 可變屬性:
  this.name = name;
  this.percent = percent; // % of precincts reported
  this.parties = parties; // political parties
  // 返回一段HTML
  this.render = function() {
    var lis = this.parties.map(function(p) {
      return '' + p.name + ': ' + p.votes + '';
    });
    var output = '' + this.name + '';
    output += '' + lis.join('') + '';
    output += 'Percent reported: ' + this.percent;
    return output;
  }
}

function getRegions(data) {
  return JSON.parse(data).map(function(obj) {
    return new Region(obj.name, obj.percent, obj.parties);
  });
}
var url = 'http://api.server.com/election-data?format=json';
var data = jQuery.ajax(url);
var regions = getRegions(data);
app.container.innerHTML = regions.map(function(r) {
  return r.render();
}).join('');

上面的代碼能夠知足對靜態選舉結果列表的展現,然而咱們須要一種動態更新選區的方式。是時候來點Bacon和FRP了。 編程

響應式編程

bacon有個函數,Bacon.fromPoll(),用於建立事件流,它讓事件成爲在必定時間間隔被調用的函數。 還有stream.subscribe()函數讓咱們能夠訂閱(subsribe)一個針對這個流的處理函數。因爲它是惰性的, 若是沒有訂閱者(subscriber)流實際上就不去作任何事情。 json

var eventStream = Bacon.fromPoll(10000, function() {
  return Bacon.Next;
});
var subscriber = eventStream.subscribe(function() {
  var url = 'http://api.server.com/election-data?format=json';
  var data = jQuery.ajax(url);
  var newRegions = getRegions(data);
  container.innerHTML = newRegions.map(function(r) {
    return r.render();
  }).join('');
});

大致上就是把它放到一個每10秒運行一次的循環中,咱們的工做完成了。可是這個方法將會不斷的ping網絡, 而且很是低效。這還不是很函數式。讓咱們繼續深刻挖掘一下Bacon.js庫。 api

Beacon裏有EventStreams和Properties參數。Properties能夠想做是「魔術」變量,它隨着事件的響應不斷變化。 實際上這不是魔術,由於他們依賴於事件流。屬性的不斷變化與事件流相關。 數組

Bacon.js裏還有一個戲法。Bacon.fromPromise()函數是一種利用promise把事件搞成流的方式。 jQuery從1.5.0版本開始實現了promise接口,因此咱們所要作的僅僅是寫一個AJAX查詢函數, 在異步調用完成時觸發事件。每當promise被處理時,他就調用EventStream的訂閱者(subscribers)。 promise

var url = 'http://api.server.com/election-data?format=json';
var eventStream = Bacon.fromPromise(jQuery.ajax(url));
var subscriber = eventStream.onValue(function(data) {
  newRegions = getRegions(data);
  container.innerHTML = newRegions.map(function(r) {
    return r.render();
  }).join('');
}

promise能夠想成是一個初始值;經過Bacon.js,咱們能夠對初始值惰性地等待。

把它們擱到一起

如今咱們學完了響應式的內容,最後咱們能夠用些代碼來玩一玩它。

咱們能夠經過對純函數的鏈式調用改變訂閱者來作些事情,好比求和或者過濾不想要的結果, 咱們只需經過咱們所建立的對按鈕的onclick()處理函數來完成這些。

// 在函數外建立事件流
var eventStream = Bacon.onPromise(jQuery.ajax(url));
var subscribe = null;
var url = 'http://api.server.com/election-data?format=json';
// 未被改變的訂閱者
$('button#showAll').click(function() {
  var subscriber = eventStream.onValue(function(data) {
    var newRegions = getRegions(data).map(function(r) {
      return new Region(r.name, r.percent, r.parties);
    });
    container.innerHTML = newRegions.map(function(r) {
      return r.render();
    }).join('');
  });
});
// 顯示所有選舉狀況的按鈕
$('button#showTotal').click(function() {
  var subscriber = eventStream.onValue(function(data) {
    var emptyRegion = new Region('empty', 0, [{
      name: 'Republican',
      votes: 0
    }, {
      name: 'Democrat',
      votes: 0
    }]);
    var totalRegions = getRegions(data).reduce(function(r1, r2) {
      newParties = r1.parties.map(function(x, i) {
        return {
          name: r1.parties[i].name,
          votes: r1.parties[i].votes + r2.parties[i].votes
        };
      });
      newRegion = new Region('Total', (r1.percent + r2.percent) / 2,
        newParties);
      return newRegion;
    }, emptyRegion);
    container.innerHTML = totalRegions.render();
  });
});
// 只顯示報告大於50%的選區
$('button#showMostlyReported').click(function() {
  var subscriber = eventStream.onValue(function(data) {
    var newRegions = getRegions(data).map(function(r) {
      if (r.percent > 50) return r;
      else return null;
    }).filter(function(r) {
      return r != null;
    });
    container.innerHTML = newRegions.map(function(r) {
      return r.render();
    }).join('');
  });
});

它的美麗之處在於:當用戶點擊按鈕時,事件流並無變化,可是訂閱者變化了,這就使全部的工做很平順。

第四章總結

JavaScript是一門美麗的語言。 它內在美確是因函數式編程而閃耀。它賦予了它良好的擴展性。事實上,打開了函數式的閘門使得頭等函數能夠作如此之多的事情。 概念互相堆疊,愈來愈高。

在這章,咱們深刻了函數式編程的範例,包括函數工廠、柯里化、函數組合等等。咱們用這些概念創建了很是模塊化的應用。 而後咱們展現瞭如何使用一些函數庫利用一樣的概念,函數組合,來控制執行順序。

這章覆蓋了幾個函數式編程的風格:數據泛型編程、基本上函數式的編程以及函數響應式編程。 他們之間並無很大不一樣,只是在不一樣場景應用的不一樣函數式編程模式而已。

上一章粗略說起了一些叫範疇論的東西,下一章咱們將學習關於它的更多內容以及如何使用它。

下一章 範疇輪
相關文章
相關標籤/搜索