frp in practice

57
Functional Reactive Programming 実践編 画面作成、リクエスト処理 @rf0444

Upload: rf0444

Post on 18-Dec-2014

517 views

Category:

Documents


1 download

DESCRIPTION

 

TRANSCRIPT

Page 1: FRP in Practice

Functional Reactive Programming 実践編~ 画面作成、リクエスト処理 ~

@rf0444

Page 2: FRP in Practice

利用ライブラリ

• Bacon.js

• https://github.com/raimohanska/bacon.js

• jQuery

Page 3: FRP in Practice

おしながき

• EventStream と Property

•画面を作る

•リクエスト処理

Page 4: FRP in Practice

EventStream と Property

Page 5: FRP in Practice

EventStream

• 発生するイベントの列 を表す

• クリックされた、など

時間

Page 6: FRP in Practice

Property

• 時間によって変化する値 を表す

• View に表示する値、最後に返ってきたレスポンス など

時間

Page 7: FRP in Practice

EventStream / Property

• EventStream#merge(EventStream)

• 2つの EventStream をくっつけた EventStream を作る。 時間

時間

時間

e1

e2

e1.merge(e2)

Page 8: FRP in Practice

EventStream / Property

• EventStream#toProperty([initVal])

• EventStream に 値が流れてくるタイミングで、値が変化する Property を作る。

• 引数に初期値を指定できる。(なしも可)

時間

時間

v0

es

es.toProperty(v0)

Page 9: FRP in Practice

EventStream / Property

• Property#changes()

• Property の値が変化したタイミングで、変化後の値が流れる EventStream を作る。

• 初期値は流れない

時間

値 p.changes()

時間

値 p

Page 10: FRP in Practice

EventStream / Property

• Property#sampledBy(EventStream)

• EventStream に値が流れた時点の Property の値が流れる EventStream を作る。

時間

時間

時間

p

es

p.sampledBy(es)

Page 11: FRP in Practice

画面を作る

Page 12: FRP in Practice

設計方針

• 出来るだけ副作用を排除したい。

• Callback 内処理を、単純な副作用だけにしたい。

Page 13: FRP in Practice

例: Click Counter

クリックすると増える

Page 14: FRP in Practice

例: Click Counter

$(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = function(conf) { var el = $('<button />').text(conf.text); return { el: el, streams: { clicked: el.asEventStream('click') } }; }; var mkText = function(conf) { var el = $('<span />'); conf.text.assign(function(text) { el.text(text); }); return { el: el }; }; var button = mkButton({ text: 'click' }); var text = mkText({ text: button.streams.clicked .map(constant(1)) .scan(0, function(a, b) { return a + b; }), }); $('body').append(button.el).append(' ').append(text.el);});

Page 15: FRP in Practice

例: Click Counter

$(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = function(conf) { var el = $('<button />').text(conf.text); return { el: el, streams: { clicked: el.asEventStream('click') } }; }; var mkText = function(conf) { var el = $('<span />'); conf.text.assign(function(text) { el.text(text); }); return { el: el }; }; var button = mkButton({ text: 'click' }); var text = mkText({ text: button.streams.clicked .map(constant(1)) .scan(0, function(a, b) { return a + b; }), }); $('body').append(button.el).append(' ').append(text.el);});

ボタン右のテキストの値(時間によって変化する)

Page 16: FRP in Practice

例: Click Counter

$(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = function(conf) { var el = $('<button />').text(conf.text); return { el: el, streams: { clicked: el.asEventStream('click') } }; }; var mkText = function(conf) { var el = $('<span />'); conf.text.assign(function(text) { el.text(text); }); return { el: el }; }; var button = mkButton({ text: 'click' }); var text = mkText({ text: button.streams.clicked .map(constant(1)) .scan(0, function(a, b) { return a + b; }), }); $('body').append(button.el).append(' ').append(text.el);});

クリックされたら 1 が流れてくる EventStream

Page 17: FRP in Practice

例: Click Counter

$(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = function(conf) { var el = $('<button />').text(conf.text); return { el: el, streams: { clicked: el.asEventStream('click') } }; }; var mkText = function(conf) { var el = $('<span />'); conf.text.assign(function(text) { el.text(text); }); return { el: el }; }; var button = mkButton({ text: 'click' }); var text = mkText({ text: button.streams.clicked .map(constant(1)) .scan(0, function(a, b) { return a + b; }), }); $('body').append(button.el).append(' ').append(text.el);});

クリックされたら 1 が流れてくる EventStream

0 から順に、足して畳み込んでいく(初期値 0 の Property ができる)

Page 18: FRP in Practice

例: Click Counter

$(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = function(conf) { var el = $('<button />').text(conf.text); return { el: el, streams: { clicked: el.asEventStream('click') } }; }; var mkText = function(conf) { var el = $('<span />'); conf.text.assign(function(text) { el.text(text); }); return { el: el }; }; var button = mkButton({ text: 'click' }); var text = mkText({ text: button.streams.clicked .map(constant(1)) .scan(0, function(a, b) { return a + b; }), }); $('body').append(button.el).append(' ').append(text.el);});

副作用(値変化時の処理登録、テキストの中身を変更)

副作用(DOM 要素登録)

Page 19: FRP in Practice

例: Counting Button

クリックすると増える

Page 20: FRP in Practice

例: Counting Button

クリックすると増える

Property を作るために、作成後の button の EventStream が必要

Page 21: FRP in Practice

例: Counting Button

クリックすると増える

Property を作るために、作成後の button の EventStream が必要

EventStream -> Property な関数を渡すようにしてみる

Page 22: FRP in Practice

例: Counting Button

$(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = function(conf) { var el = $('<button />'); var streams = { clicked: el.asEventStream('click') }; var properties = conf.f(streams); properties.text.assign(function(text) { el.text(text); }); return { el: el }; }; var button = mkButton({ f: function(streams) { return { text: streams.clicked .map(constant(1)) .scan(0, function(a, b) { return a + b; }), }; }, }); $('body').append(button.el);});

Page 23: FRP in Practice

例: Counting Button

$(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = function(conf) { var el = $('<button />'); var streams = { clicked: el.asEventStream('click') }; var properties = conf.f(streams); properties.text.assign(function(text) { el.text(text); }); return { el: el }; }; var button = mkButton({ f: function(streams) { return { text: streams.clicked .map(constant(1)) .scan(0, function(a, b) { return a + b; }), }; }, }); $('body').append(button.el);});

EventStream -> Property な関数

Page 24: FRP in Practice

例: Counting Button

$(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = function(conf) { var el = $('<button />'); var streams = { clicked: el.asEventStream('click') }; var properties = conf.f(streams); properties.text.assign(function(text) { el.text(text); }); return { el: el }; }; var button = mkButton({ f: function(streams) { return { text: streams.clicked .map(constant(1)) .scan(0, function(a, b) { return a + b; }), }; }, }); $('body').append(button.el);});

渡された EventStream を畳み込んで、Propertyを作る

Page 25: FRP in Practice

例: Counting Button

$(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = function(conf) { var el = $('<button />'); var streams = { clicked: el.asEventStream('click') }; var properties = conf.f(streams); properties.text.assign(function(text) { el.text(text); }); return { el: el }; }; var button = mkButton({ f: function(streams) { return { text: streams.clicked .map(constant(1)) .scan(0, function(a, b) { return a + b; }), }; }, }); $('body').append(button.el);});

先に EventStream を作っておいて、

Page 26: FRP in Practice

例: Counting Button

$(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = function(conf) { var el = $('<button />'); var streams = { clicked: el.asEventStream('click') }; var properties = conf.f(streams); properties.text.assign(function(text) { el.text(text); }); return { el: el }; }; var button = mkButton({ f: function(streams) { return { text: streams.clicked .map(constant(1)) .scan(0, function(a, b) { return a + b; }), }; }, }); $('body').append(button.el);});

渡された関数に適用して、Property を得る

先に EventStream を作っておいて、

Page 27: FRP in Practice

$(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = function(conf) { var el = $('<button />'); var streams = { clicked: el.asEventStream('click') }; var properties = conf.f(streams); properties.text.assign(function(text) { el.text(text); }); return { el: el }; }; var button = mkButton({ f: function(streams) { return { text: streams.clicked .map(constant(1)) .scan(0, function(a, b) { return a + b; }), }; }, }); $('body').append(button.el);});

例: Counting Button

副作用(値変化時の処理登録、テキストの中身を変更)

副作用(DOM 要素登録)

Page 28: FRP in Practice

例: Cross Counting Button

クリックすると 反対側が増える

Page 29: FRP in Practice

例: Cross Counting Button

クリックすると 反対側が増える

Property を作るために、別の button の EventStream が必要

(相互に要求)

Page 30: FRP in Practice

例: Cross Counting Button

クリックすると 反対側が増える

Property を作るために、別の button の EventStream が必要

(相互に要求)

Bus を使い、一旦 EventStream として渡しておき、

button を作るときに Bus につなぐ

Page 31: FRP in Practice

例: Cross Counting Button$(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = { streams: function() { return { clicked: new Bacon.Bus() }; }, create: function(conf) { var el = $('<button />'); conf.properties.text.assign(function(text) { el.text(text); }); conf.streams.clicked.plug(el.asEventStream('click')); return { el: el }; }, }; var streams1 = mkButton.streams(); var streams2 = mkButton.streams(); var logic = function(s) { return s.clicked.map(constant(1)).scan(0, function(a, b) { return a + b; }); }; var button1 = mkButton.create({ properties: { text: logic(streams2) }, streams: streams1, }); var button2 = mkButton.create({ properties: { text: logic(streams1) }, streams: streams2, }); $('body').append(button1.el).append(' ').append(button2.el);});

Page 32: FRP in Practice

例: Cross Counting Button$(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = { streams: function() { return { clicked: new Bacon.Bus() }; }, create: function(conf) { var el = $('<button />'); conf.properties.text.assign(function(text) { el.text(text); }); conf.streams.clicked.plug(el.asEventStream('click')); return { el: el }; }, }; var streams1 = mkButton.streams(); var streams2 = mkButton.streams(); var logic = function(s) { return s.clicked.map(constant(1)).scan(0, function(a, b) { return a + b; }); }; var button1 = mkButton.create({ properties: { text: logic(streams2) }, streams: streams1, }); var button2 = mkButton.create({ properties: { text: logic(streams1) }, streams: streams2, }); $('body').append(button1.el).append(' ').append(button2.el);});

button から出るEventStream を Bus として取得できるようにしておく

Page 33: FRP in Practice

例: Cross Counting Button$(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = { streams: function() { return { clicked: new Bacon.Bus() }; }, create: function(conf) { var el = $('<button />'); conf.properties.text.assign(function(text) { el.text(text); }); conf.streams.clicked.plug(el.asEventStream('click')); return { el: el }; }, }; var streams1 = mkButton.streams(); var streams2 = mkButton.streams(); var logic = function(s) { return s.clicked.map(constant(1)).scan(0, function(a, b) { return a + b; }); }; var button1 = mkButton.create({ properties: { text: logic(streams2) }, streams: streams1, }); var button2 = mkButton.create({ properties: { text: logic(streams1) }, streams: streams2, }); $('body').append(button1.el).append(' ').append(button2.el);});

先に EventStream だけ取得しておいて、

button から出るEventStream を Bus として取得できるようにしておく

Page 34: FRP in Practice

例: Cross Counting Button$(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = { streams: function() { return { clicked: new Bacon.Bus() }; }, create: function(conf) { var el = $('<button />'); conf.properties.text.assign(function(text) { el.text(text); }); conf.streams.clicked.plug(el.asEventStream('click')); return { el: el }; }, }; var streams1 = mkButton.streams(); var streams2 = mkButton.streams(); var logic = function(s) { return s.clicked.map(constant(1)).scan(0, function(a, b) { return a + b; }); }; var button1 = mkButton.create({ properties: { text: logic(streams2) }, streams: streams1, }); var button2 = mkButton.create({ properties: { text: logic(streams1) }, streams: streams2, }); $('body').append(button1.el).append(' ').append(button2.el);});

先に EventStream だけ取得しておいて、

EventStream から Property を作成

button から出るEventStream を Bus として取得できるようにしておく

Page 35: FRP in Practice

例: Cross Counting Button$(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = { streams: function() { return { clicked: new Bacon.Bus() }; }, create: function(conf) { var el = $('<button />'); conf.properties.text.assign(function(text) { el.text(text); }); conf.streams.clicked.plug(el.asEventStream('click')); return { el: el }; }, }; var streams1 = mkButton.streams(); var streams2 = mkButton.streams(); var logic = function(s) { return s.clicked.map(constant(1)).scan(0, function(a, b) { return a + b; }); }; var button1 = mkButton.create({ properties: { text: logic(streams2) }, streams: streams1, }); var button2 = mkButton.create({ properties: { text: logic(streams1) }, streams: streams2, }); $('body').append(button1.el).append(' ').append(button2.el);});

button から出るEventStream を Bus として取得できるようにしておく

先に EventStream だけ取得しておいて、

一緒に EventStream (Bus) を渡す

EventStream から Property を作成

Page 36: FRP in Practice

例: Cross Counting Button$(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = { streams: function() { return { clicked: new Bacon.Bus() }; }, create: function(conf) { var el = $('<button />'); conf.properties.text.assign(function(text) { el.text(text); }); conf.streams.clicked.plug(el.asEventStream('click')); return { el: el }; }, }; var streams1 = mkButton.streams(); var streams2 = mkButton.streams(); var logic = function(s) { return s.clicked.map(constant(1)).scan(0, function(a, b) { return a + b; }); }; var button1 = mkButton.create({ properties: { text: logic(streams2) }, streams: streams1, }); var button2 = mkButton.create({ properties: { text: logic(streams1) }, streams: streams2, }); $('body').append(button1.el).append(' ').append(button2.el);});

渡された Bus に、クリックの EventStream をつなげる

Page 37: FRP in Practice

例: Cross Counting Button$(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = { streams: function() { return { clicked: new Bacon.Bus() }; }, create: function(conf) { var el = $('<button />'); conf.properties.text.assign(function(text) { el.text(text); }); conf.streams.clicked.plug(el.asEventStream('click')); return { el: el }; }, }; var streams1 = mkButton.streams(); var streams2 = mkButton.streams(); var logic = function(s) { return s.clicked.map(constant(1)).scan(0, function(a, b) { return a + b; }); }; var button1 = mkButton.create({ properties: { text: logic(streams2) }, streams: streams1, }); var button2 = mkButton.create({ properties: { text: logic(streams1) }, streams: streams2, }); $('body').append(button1.el).append(' ').append(button2.el);});

副作用(値変化時の処理登録、テキストの中身を変更)

副作用(DOM 要素登録)

副作用(Bus につなぐ)

Page 38: FRP in Practice

リクエスト処理

Page 39: FRP in Practice

Bacon.fromPromise

• jQuery の ajax 系メソッドが返す Promise オブジェクトから、EventStream を作る。

Page 40: FRP in Practice

Bacon.fromPromise

• jQuery の ajax 系メソッドが返す Promise オブジェクトから、EventStream を作る。

assign は、イベント登録を解除する関数を返す

流れてきたレスポンス

Page 41: FRP in Practice

• レスポンスがエラーの場合、そのままでは assign 等に流れていかない。

エラーレスポンスの処理

Page 42: FRP in Practice

エラーレスポンスの処理

• レスポンスがエラーの場合、そのままでは assign 等に流れていかない。

• mapError メソッドを使って、エラー系の流れを変換関数を通して本流へ流す。

流れてきたエラーレスポンス

エラー系の流れをそのまま本流へ

Page 43: FRP in Practice

EventStream#flatMap/flatMapLatest

• EventStream が流れてくる EventStream を、中の EventStream に流れる値が流れてくる EventStream にする

引用: https://github.com/raimohanska/bacon.js/wiki/Diagrams

引用: https://github.com/raimohanska/bacon.js/wiki/Diagrams

flatMap flatMapLatest

Page 44: FRP in Practice

EventStream#flatMap/flatMapLatest

• EventStream が流れてくる EventStream を、中の EventStream に流れる値が流れてくる EventStream にする

引用: https://github.com/raimohanska/bacon.js/wiki/Diagrams

引用: https://github.com/raimohanska/bacon.js/wiki/Diagrams

flatMap flatMapLatest全部流す

後の結果の方が速ければ、前の結果は流れない

Page 45: FRP in Practice

リクエスト処理の例右に入力したパスに

GET リクエストを飛ばす

返ってきたレスポンスを表示

Page 46: FRP in Practice

ヘルパ

var constant = function(x) { return function() { return x; }; };var id = function(x) { return x; };var left = function(x) { return function(f, g) { return f(x); }; };var right = function(x) { return function(f, g) { return g(x); }; };

var mkButton = { streams: function() { return { clicked: new Bacon.Bus() }; }, create: function(conf) { var el = $('<button />'); conf.properties.text.assign(function(text) { el.text(text); }); conf.properties.enable.assign(function(enable) { el.attr('disabled', !enable); }); conf.streams.clicked.plug(el.asEventStream('click')); return { el: el }; },};

var mkTextarea = function(conf) { var el = $('<textarea />'); conf.val.assign(function(text) { el.val(text); }); return { el: el };};

Page 47: FRP in Practice

ヘルパ

var constant = function(x) { return function() { return x; }; };var id = function(x) { return x; };var left = function(x) { return function(f, g) { return f(x); }; };var right = function(x) { return function(f, g) { return g(x); }; };

var mkButton = { streams: function() { return { clicked: new Bacon.Bus() }; }, create: function(conf) { var el = $('<button />'); conf.properties.text.assign(function(text) { el.text(text); }); conf.properties.enable.assign(function(enable) { el.attr('disabled', !enable); }); conf.streams.clicked.plug(el.asEventStream('click')); return { el: el }; },};

var mkTextarea = function(conf) { var el = $('<textarea />'); conf.val.assign(function(text) { el.val(text); }); return { el: el };};

Either

Page 48: FRP in Practice

ヘルパ

var constant = function(x) { return function() { return x; }; };var id = function(x) { return x; };var left = function(x) { return function(f, g) { return f(x); }; };var right = function(x) { return function(f, g) { return g(x); }; };

var mkButton = { streams: function() { return { clicked: new Bacon.Bus() }; }, create: function(conf) { var el = $('<button />'); conf.properties.text.assign(function(text) { el.text(text); }); conf.properties.enable.assign(function(enable) { el.attr('disabled', !enable); }); conf.streams.clicked.plug(el.asEventStream('click')); return { el: el }; },};

var mkTextarea = function(conf) { var el = $('<textarea />'); conf.val.assign(function(text) { el.val(text); }); return { el: el };};

ボタンの 有効/無効 もProperty で受け取る

Page 49: FRP in Practice

ヘルパ

var constant = function(x) { return function() { return x; }; };var id = function(x) { return x; };var left = function(x) { return function(f, g) { return f(x); }; };var right = function(x) { return function(f, g) { return g(x); }; };

var mkButton = { streams: function() { return { clicked: new Bacon.Bus() }; }, create: function(conf) { var el = $('<button />'); conf.properties.text.assign(function(text) { el.text(text); }); conf.properties.enable.assign(function(enable) { el.attr('disabled', !enable); }); conf.streams.clicked.plug(el.asEventStream('click')); return { el: el }; },};

var mkTextarea = function(conf) { var el = $('<textarea />'); conf.val.assign(function(text) { el.val(text); }); return { el: el };};

出力用テキストエリア設定は出力値 Property のみ。

Page 50: FRP in Practice

実装var input = $('<input type="text" />').width(400);var bs = mkButton.streams();var request = bs.clicked.map(function() { return input.val(); });var response = request.flatMapLatest(function(url) { return Bacon.fromPromise($.get(url)).map(right) .mapError(function(e) { return left(e.responseText); });});var button = mkButton.create({ properties: { text: Bacon.constant('request'), enable: Bacon.once(true) .merge(request.map(constant(false))) .merge(response.map(constant(true))) .toProperty(), }, streams: bs,});var output = mkTextarea({ val: response.map(function(r) { return r(function(msg) { return 'error - ' + msg; }, id); }).toProperty(),});$('body') .append($('<p />').append(input).append(' ').append(button.el)) .append($('<p />').append(output.el.width(500).height(200)));

Page 51: FRP in Practice

実装var input = $('<input type="text" />').width(400);var bs = mkButton.streams();var request = bs.clicked.map(function() { return input.val(); });var response = request.flatMapLatest(function(url) { return Bacon.fromPromise($.get(url)).map(right) .mapError(function(e) { return left(e.responseText); });});var button = mkButton.create({ properties: { text: Bacon.constant('request'), enable: Bacon.once(true) .merge(request.map(constant(false))) .merge(response.map(constant(true))) .toProperty(), }, streams: bs,});var output = mkTextarea({ val: response.map(function(r) { return r(function(msg) { return 'error - ' + msg; }, id); }).toProperty(),});$('body') .append($('<p />').append(input).append(' ').append(button.el)) .append($('<p />').append(output.el.width(500).height(200)));

ボタンがクリックされると入力値が流れる EventStream

Page 52: FRP in Practice

実装var input = $('<input type="text" />').width(400);var bs = mkButton.streams();var request = bs.clicked.map(function() { return input.val(); });var response = request.flatMapLatest(function(url) { return Bacon.fromPromise($.get(url)).map(right) .mapError(function(e) { return left(e.responseText); });});var button = mkButton.create({ properties: { text: Bacon.constant('request'), enable: Bacon.once(true) .merge(request.map(constant(false))) .merge(response.map(constant(true))) .toProperty(), }, streams: bs,});var output = mkTextarea({ val: response.map(function(r) { return r(function(msg) { return 'error - ' + msg; }, id); }).toProperty(),});$('body') .append($('<p />').append(input).append(' ').append(button.el)) .append($('<p />').append(output.el.width(500).height(200)));

入力が流れてきたら、リクエストを飛ばす

Page 53: FRP in Practice

実装var input = $('<input type="text" />').width(400);var bs = mkButton.streams();var request = bs.clicked.map(function() { return input.val(); });var response = request.flatMapLatest(function(url) { return Bacon.fromPromise($.get(url)).map(right) .mapError(function(e) { return left(e.responseText); });});var button = mkButton.create({ properties: { text: Bacon.constant('request'), enable: Bacon.once(true) .merge(request.map(constant(false))) .merge(response.map(constant(true))) .toProperty(), }, streams: bs,});var output = mkTextarea({ val: response.map(function(r) { return r(function(msg) { return 'error - ' + msg; }, id); }).toProperty(),});$('body') .append($('<p />').append(input).append(' ').append(button.el)) .append($('<p />').append(output.el.width(500).height(200)));

正常レスポンスを Right で包んでおいて、

Page 54: FRP in Practice

実装var input = $('<input type="text" />').width(400);var bs = mkButton.streams();var request = bs.clicked.map(function() { return input.val(); });var response = request.flatMapLatest(function(url) { return Bacon.fromPromise($.get(url)).map(right) .mapError(function(e) { return left(e.responseText); });});var button = mkButton.create({ properties: { text: Bacon.constant('request'), enable: Bacon.once(true) .merge(request.map(constant(false))) .merge(response.map(constant(true))) .toProperty(), }, streams: bs,});var output = mkTextarea({ val: response.map(function(r) { return r(function(msg) { return 'error - ' + msg; }, id); }).toProperty(),});$('body') .append($('<p />').append(input).append(' ').append(button.el)) .append($('<p />').append(output.el.width(500).height(200)));

正常レスポンスを Right で包んでおいて、

エラーレスポンスは Left で包んで本流へ

Page 55: FRP in Practice

実装var input = $('<input type="text" />').width(400);var bs = mkButton.streams();var request = bs.clicked.map(function() { return input.val(); });var response = request.flatMapLatest(function(url) { return Bacon.fromPromise($.get(url)).map(right) .mapError(function(e) { return left(e.responseText); });});var button = mkButton.create({ properties: { text: Bacon.constant('request'), enable: Bacon.once(true) .merge(request.map(constant(false))) .merge(response.map(constant(true))) .toProperty(), }, streams: bs,});var output = mkTextarea({ val: response.map(function(r) { return r(function(msg) { return 'error - ' + msg; }, id); }).toProperty(),});$('body') .append($('<p />').append(input).append(' ').append(button.el)) .append($('<p />').append(output.el.width(500).height(200)));

レスポンスが返ってくるまではボタンを無効にする

Page 56: FRP in Practice

実装var input = $('<input type="text" />').width(400);var bs = mkButton.streams();var request = bs.clicked.map(function() { return input.val(); });var response = request.flatMapLatest(function(url) { return Bacon.fromPromise($.get(url)).map(right) .mapError(function(e) { return left(e.responseText); });});var button = mkButton.create({ properties: { text: Bacon.constant('request'), enable: Bacon.once(true) .merge(request.map(constant(false))) .merge(response.map(constant(true))) .toProperty(), }, streams: bs,});var output = mkTextarea({ val: response.map(function(r) { return r(function(msg) { return 'error - ' + msg; }, id); }).toProperty(),});$('body') .append($('<p />').append(input).append(' ').append(button.el)) .append($('<p />').append(output.el.width(500).height(200)));

正常レスポンスはそのまま出力エラーレスポンスは ‘error - ’ に続けて出力

Page 57: FRP in Practice

設計

• 実際には、streams から properties を作る部分や、リクエストを飛ばす部分は、別モジュールにしておくといい。

ViewLogic

StorageAjax

App

get streams,create from properties

create propertiesfrom streams and storages

create response-streams