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是一门美丽的语言。 它内在美确是因函数式编程而闪耀。它赋予了它良好的扩展性。事实上,打开了函数式的闸门使得头等函数能够作如此之多的事情。 概念互相堆叠,愈来愈高。

在这章,咱们深刻了函数式编程的范例,包括函数工厂、柯里化、函数组合等等。咱们用这些概念创建了很是模块化的应用。 而后咱们展现了如何使用一些函数库利用一样的概念,函数组合,来控制执行顺序。

这章覆盖了几个函数式编程的风格:数据泛型编程、基本上函数式的编程以及函数响应式编程。 他们之间并无很大不一样,只是在不一样场景应用的不一样函数式编程模式而已。

上一章粗略说起了一些叫范畴论的东西,下一章咱们将学习关于它的更多内容以及如何使用它。

下一章 范畴轮
相关文章
相关标签/搜索