Skip to content

Instantly share code, notes, and snippets.

@masakielastic
Last active May 2, 2018 05:07
Show Gist options
  • Star 38 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save masakielastic/5897831 to your computer and use it in GitHub Desktop.
Save masakielastic/5897831 to your computer and use it in GitHub Desktop.
bacon.js の README の翻訳 (原文は2013年7月1日時点のものを取得)。最新の内容は https://github.com/raimohanska/bacon.js を参照。ライセンスは原文と同じです。

Bacon.js

JavaScript のための小さな FRP (Functional Reactive Programming) ライブラリです。

手続き型から関数型に切り替えることで、イベントスパゲッティのコードがクリーンで宣言スタイルの「風水の bacon」(feng shui bacon)に変わります。入れ子のループを mapfilter のような関数型プログラミングのコンセプトに置き換えることに似ています。個別のイベントに取り組むことを止め、イベントストリームを扱います。mapfilter でデータを変換します。mergecombine でデータを combine します。重火器に切り替え、上司のように flatMapcombineTemplate を掌握します。

このライブラリはイベントのアンダースコア( _ )です。残念なことに ~ の記号は JavaScript では認められていないからです。

資料です。

筆者のおもしろくて (LOL)、インタラクティブで、solid-ass スライドショー でも確認することができます。

Abacon へのフィードバックをお願いします。お使いなさっているのであれば知らせてください。どのように使われているのか教えてください。不足しているものは?間違っていることは?ご協力お願いします。

インストール

最新の生成された JavaScript をダウンロードできます。

もしくは、script タグを使って Github から次のファイルを直接インクルードすることができます。

<script src="https://raw.github.com/raimohanska/bacon.js/master/dist/Bacon.js"></script>

0.4.2 は cdnjs のホスティングサービスからも利用できます。

http://cdnjs.cloudflare.com/ajax/libs/bacon.js/0.4.2/Bacon.js
http://cdnjs.cloudflare.com/ajax/libs/bacon.js/0.4.2/Bacon.min.js

node.js をターゲットにしているのであれば

npm install baconjs

bower ユーザーであれば

bower install bacon

イントロ

Functional Reactive Programming のアイディアは Stack Overflow において Conal Elliot によってくわしく説明されています。

Bacon.js は Functional Reactive Programming のためのライブラリです。もしくは、イベントと動的な値 (Bacon.js ではプロパティと呼ばれます) を扱うためのライブラリとも説明することができます。

ともかく、イベントソース、たとえば「要素の上でマウスをクリックする」を EventStream に包むことができます。

var cliks = $("h1").asEventStream("click")

それぞれの EventStream はイベントのストリームをあらわします。これは Observable オブジェクトです。つまり、ストリームのなかのイベントをリスニングできるということです。たとえば、次のように onValue メソッドでコールバックを指定します。

cliks.onValue(function() { alert("you clicked the h1 element") })

よりすっきりしたコードを使うこともできます。bacon.js の Bacon は複数の方法でこれらのストリームを変換、フィルタリング、combine することができることです (API を参照)。たとえば、mapfilter などのメソッドは関数型のリストプログラミングの同名の関数と似ています (Underscore など)。例です。

var plus = $("#plus").asEventStream("click").map(1)
var minus = $("#minus").asEventStream("click").map(-1)
var both = plus.merge(minus)

これによって「plus」ボタンがクリックされたときに1を出力し、「minus」ボタンがクリックされたときに別のストリームが -1 を出力します。both ストリームはマージずみのストリームで、plus と minus のストリームの両方からのイベントを含みます。これによって1つのハンドラーで両方のストリームをサブスクライブすることができます。

both.onValue(function(val) { /* val will be 1 or -1 */ })

EventStreams に加えて、bacon.js には Property と呼ばれる機能があります。これは EventStream と似ていますが、「現在の値」を持ちます。ですので、変化し現在の状態をもつものは Properties であり、離散的なイベントから構成されるものは EventStreams です。マウスクリックを EventStream、マウスの位置を Property と考えることができます。scan もしくは toProperty メソッドで EventStream から Properties をつくることができます。例です。

function add(x, y) { return x + y }
var counter = both.scan(0, add)
counter.onValue(function(sum) { $("#sum").text(sum) })

counter プロパティは both ストリームにおける値の合計値を含みます。ですので、実用の面ではプラスもしくはマイナスボタンで加算もしくは減算できるカウンターです。scan メソッドは「シード値」である 0 と「アキュムレータ関数」である add を提供することで both ストリームにおいて「現在の合計値」を計算するために使われます。scan メソッドは任意のソード値ではじまるプロパティをつくります。ソースストリームのそれぞれのイベントはアキュムレータ関数を現在のプロパティとストリームからの新しい値に適用します。

jQuery による DOM 要素への値および属性追加など、プロパティは広く使われます。span 要素が変化したときに、プロパティの値を span 要素のテキストとして割り当てる例です。

property.assign($("span"), "text")

プロパティの値の内容にもとづいて同じ span 要素を表示したり、隠したりすることは直感的です。

function hiddenForEmptyValue(value) { return value == "" ? "hidden" : "visible" }
property.map(hiddenForEmptyValue).assign($("span"), "css", "visibility")

上記の例においてプロパティの値である「hello」は「visible」にマッピングされ、Bacon の呼び出しになります。

$("span").css("visibility", "visible")

実際のチュートリアルに関して、ブログの投稿を確認してください。

API

ストリームを作成する

$.asEventStream("click") は jQuery もしくは Zepto.js オブジェクトのイベントから EventStream を作成します。jQuery のライブセレクターと/もしくは jQuery イベントとパラメータを処理する関数を追加するために、オプションの引数を渡すことができます。たとえば次のとおりです。

$("#my-div").asEventStream("click", ".more-specific-selector")
$("#my-div").asEventStream("click", ".more-specific-selector", function(event, args) { return args[0] })
$("#my-div").asEventStream("click", function(event, args) { return args[0] })

Bacon.fromPromise(promise [, abort]) は jQuery Ajax などの Promise オブジェクトから EventStream を作成します。このストリームは単独の値もしくはエラーを含み、その直後にストリームの終端が続きます。すべてのサブスクライバーが作成されたストリームから削除されたときに呼び出される任意のプロミセスの abort メソッドを用意するためにオプションの abort フラグ (すなわち、 ´fromPromise(p, true)´ を使うことができます。 でご確認ください。

Bacon.fromEventTarget(target, eventName [, eventTransformer]) は DOM EventTarget もしくは Node.JS EventEmitter オブジェクトから EventStream を作成します。エミットされたイベントのパラメーターを変換するオプションの関数を渡すこともできます。

Bacon.fromEventTarget(document.body, "click").onValue(function() { alert("Bacon!") })

Bacon.fromCallback(f) はコールバックを受け取る関数から EventStream を作成します。関数の呼び出し回数は一度だけです。例です。

Bacon.fromCallback(function(callback) {
  setTimeout(function() {
    callback("Bacon!")
  }, 1000)
})

これは単独の値である「Bacon!」を出力してその後終了するストリームを作成します。setTimeout の利用は値に1秒の遅延をもたらします。

fromCallback に任意の数の引数を渡すこともできます。このメソッドは関数に渡されます。これらの引数はシンプルな変数である、Bacon EventStreams もしくは Properties です。たとえば、次のコードは「Bacon rules」を出力します。

bacon = Bacon.constant('bacon')
Bacon.fromCallback(function(a, b, callback) {
  callback(a + ' ' + b);
}, bacon, 'rules').log();

Bacon.fromNodeCallback(f)Bacon.fromCallback と同じようにふるまいます。違いはコールバックの呼び出し方が Node.js の慣習にもとづいていることを想定していることです。callback(error, data) はすべてがよい場合、エラーは null になります。例です。

var Bacon = require('baconjs').Bacon,
    fs = require('fs');
var read = Bacon.fromNodeCallback(fs.readFile, 'input.txt');
read.onError(function(error) { console.log("Reading failed: " + error); });
read.onValue(function(value) { console.log("Read contents: " + value); });

Bacon.fromPoll(interval, f) は任意の間隔で、任意の関数をポーリングします。 関数は Bacon.Next もしくは Bacon.End のどちらかのイベントを返します。 これらがストリームへのサブスクライバーである場合にかぎり、ポーリングが行われます。 f が Bacon.End を返すときにポーリングは永続的に終わります。

Bacon.once(value) は最初のサブスクライバーのために単独の値を配信する EventStream を作成します。 ストリームはその値の直後に終わります。

Bacon.fromArray(values) は最初のサブスクライバーへの一連の値を配信する EventStream を作成します。 これらの値が配信された後でストリームは終わります。

Bacon.interval(interval, value) は任意の間隔で単独の要素を無限に繰り返します (ミリ秒単位)

Bacon.sequentially(interval, values) は任意の値 (配列) を含むストリームを作成します。ミリ秒単位の間隔で配信されます。

Bacon.repeatedly(interval, values) はミリ秒単位で要素を無限に繰り返します。 たとえば、sequentially(10, [1,2,3]) は 1,2,3,1,2,3... になります。

Bacon.never() は即座に終わる EventStream を作成します。

Bacon.later(delay, value) は遅延の後で値を生み出す単独の要素のストリームを作成します (ミリ秒単位)。

new Bacon.EventStream(subscribe) はサブスクライバー関数をもつイベントストリームを作成します (下記を参照)

property.changes() は Property への変更のストリームを作成します (下記の Property API を参照)

new Bacon.Bus() はプッシュ可能/プラガブルなストリームを作成します (下記の Bus セクションを参照)

Pro tip: プレーンな値の代わりに Bacon.Error オブジェクトを使うことで、上記のコンストラクターによって作成されたストリームに Errors を追加することもできます。

カスタムストリームのための EventStream コンストラクター

上記のファクトリメソッドがなければ、コンストラクターを使って独自の EventStream を運用することもできます。

new EventStream(subscribe)

パラメーターの subscribe は Events を受け取る関数であるサブスクライバーを受け入れる関数です。

例です:

new Bacon.EventStream(function(subscriber) {
  subscriber(new Bacon.Next("a value here"))
  subscriber(new Bacon.Next(function() {
    return "This one will be evaluated lazily"
  }))
  subscriber(new Bacon.Error("oops, an error"))
  subscriber(new Bacon.End())
  return function() { // unsub functionality here, this one's a no-op }
})

サブスクライバー関数は関数を返さなければなりません。そのような関数を unsubscribe と呼ぶことにしましょう。戻り値の関数はサブスクライブ解除のためにサブスクライバーによって使うことが可能で、サブスクラブ関数が保ったすべてのリソースを解放します。

サブスクライバー関数は Bacon.more もしくは Bacon.noMore を返します。undefined もしくは何かを返すこともあります。 Bacon.noMore を返すとき, サブスクライバーはサブスクライブ解除の関数を呼び出す場合に備えて一掃されなければなりません。

EventStream のコンストラクターはサブスクライブ関数をつつむので、 最初のストリームリスナーが追加されたときにかぎり呼び出されます。

サブスクライブ解除関数は最後のリスナーが削除された後にかぎり呼び出されます。 サブスクライブ関数の複数の呼び出しのために、サブスクライブーサブスクライブ解除のサイクルは無限に繰り返されることがあります。

new Bacon.Next(..) コンストラクターに関する注です。次のように書くことができます。

new Bacon.Next("value")

しかし正規の書き方は次のようになります。

new Bacon.Next(function() { return "value") })

関数ではないストリームの実際の値を知るときに後者が唯一安全な方法です。

プレーンな値の代わりに関数を使うアイディアの由来は Bacon.js の内部において mapcombine によってつくられた値の評価を延期することで遅延評価を利用しているからです。

EventStreams と Properties の共通のメソッド

EventStream と Property の両方で Observable インターフェイス、ゆえにたくさんのメソッドを共有します。

observable.map(f) は任意の関数を使って値を写し EventStream を返します。関数の代わりに定数を提供することもできます。 さらに、「.keyCode」のようなプロパティ抽出子の文字列を使うことができます。ですので、f がドットではじまる文字列の場合、要素はイベントの値の対応するフィールド/関数に写されます。たとえば、map(".keyCode") は入力値からの keyCode フィールドを引き抜きます。keyCode が関数であれば、結果のストリームは関数によって返される値を含みます。下記の関数のコンストラクションルールが適用されます。

stream.map(property) はストリームのイベントを任意のプロパティの現在の値に写します。これは property.sampledBy(stream) と同等です。

observable.mapError(f) は任意の関数を使って、エラーを写します。 具体的に説明すると、関数にエラーイベントの「error」フィードを提供し、 戻り値にもとづいて「Next」イベントを生成します。関数のコンストラクションルールが適用されます。 undefined の値をもつ Next イベントを生み出す引数を省略できます。

observable.mapEnd(f) は End イベントの前に Next イベントを追加します。 ソースストリームが終了するときに任意の関数を呼び出すことで値がつくられます。 関数の代わりに、スタティックな値を使うことができます。undefined の値をもつ Next イベントを生成するために引数を省略できます。

observable.filter(f) は述語関数を使って値をフィルタリングすることができます。 関数の代わりに、定数の値 (true/false) もしくはプロパティ抽出子の文字列 (「.isValuable」など) を使うことができます。map のように使えます。

observable.filter(property) はプロパティの値にもとづいて値をフィルタリングします。イベントのときに プロパティに true の値が含まれるのであれば、イベントは出力に含まれます。

observable.takeWhile(f) は要素が述語関数が true を満たすかぎり、while 文を実行し続けます。

observable.take(n) はストリームから最大で n 個の要素をとります。 Bacon.never() if n <= 0 と等しいです。

observable.takeUntil(stream2) はほかのストリームで Next イベントがあらわれるまで、ソースから要素をとります。 ほかのストリームが値なしで終わる場合、無視されます。

observable.skip(n) はストリームの最初から n 番目の要素をスキップします。

observable.delay(delay) はミリ秒単位の総量によってストリーム/プロパティを遅延させます。Property の初期値は遅延させません。

var delayed = source.delay(2)
source:    asdf----asdf----
delayed:   --asdf----asdf--

observable.throttle(delay) はミリ秒単位の量でストリーム/プロパティを throttle させます。 イベントは delay の最小の間隔でエミットされます。実装は stream.bufferWithTime にもとづいています。 Property の初期値をエミットする影響はありません。

var throttled = source.throttle(2)
source:    asdf----asdf----
throttled: --s--f----s--f--

observable.debounce(delay) はミリ秒単位の量でストリーム/プロパティを throttle します。 「quiet period」の後にのみイベントはエミットされます。Property の初期値のエミットには影響を及ぼしません。 throttledebounce の違いは jQuery の同名のメソッドと同じです。

source:             asdf----asdf----
source.debounce(2): -----f-------f--

observable.debounceImmediate(delay) はストリームの最初のイベントを pass しますが、その他、以前の出力から任意のミリ秒が pass した後にかぎり、イベントが pass します。

source:                      asdf----asdf----
source.debounceImmediate(2): a-d-----a-d-----

observable.doAction(f) はストリーム/プロパティを返します。サブスクライバーにディスパッチする前に、それぞれの値に対して関数 f が実行されます。 これはデバッグに役立ちますが、イベントに対して preventDefault() メソッドを呼び出すようなものに対しても有用です。 実際、「.preventDefault」 のような関数の代わりに、プロパティ抽出しの文字列を使うことができます。

observable.not() はブール値を逆転させるストリーム/プロパティを返します。

observable.flatMap(f) はソースストリームのそれぞれの要素に対して、 関数 f を使って、新しいストリームを spawn させます。spawn された それぞれのストリームからイベントを結果の EventStream に collect します。 これは RxJs の selectMany ととてもよく似ています。 関数の代わりに、ストリーム/プロパティを提供することもできます。 また、関数の f の戻り値は Observable (ストリーム/プロパティ) もしくは定数値のどちらかです。 flatMap の結果は常に EventStream です。

結果の一部だけを含めて、変換とフィルタリングを同時に行うために、stream.flatMap() は Bacon.once()Bacon.never() と一緒によく使われます。

例 - 空の値をスキップし、文字列を整数に変換します。

stream.flatMap(function(text) {
    return (text != "") ? parseInt(text) : Bacon.never()
})

observable.flatMapLatest(f) lは flatMap と似ていますが、 すべての spawn されたストリームからイベントを含む代わりに、 最新の spawn されたストリームからのイベントのみを含みます。 これをストリームからストリームへの切り替えと考えることができます。 このメソッドの古い名前は switch です。 関数の代わりに、ストリーム/プロパティも提供します。

observable.flatMapFirst(f) は flatMap のように、 以前 spawn されたストリームが終了した場合のみ、新しいストリームを spawn しません。

observable.scan(seed, f) は任意のシード値とアキュムレータ関数でストリーム/プロパティをスキャンし、Property を結果値として返します。 たとえば、「integral」なプロパティをつくるために、ゼロをシードとし、「plus」関数をアキュムレータとして使うことができます。

関数の代わりに、「.concat」といったメソッド名をつけることもできます。 この場合、このメソッドはアキュムレータの値で呼び出され、新しいストリームの値は引数として使われます。

例:

var plus = function (a,b) { return a + b }
Bacon.sequentially(1, [1,2,3]).scan(0, plus)

上記のコードは結果のストリームにおける次の要素になります。

seed value = 0
0 + 1 = 1
1 + 2 = 3
3 + 3 = 6

r = p.scan(f,seed) のように Property に適用される場合、(おそらくはささいなことですが)落とし穴があります。 r に対するはじまりの値は scan が適用されたときに p が初期値をもつかどうかによります。 初期値がなければ、このメソッドは EventStream.scan と等しく動きます。seedr の初期値です。

しかしながら、r がすでに現在/初期値の x をもっている場合、シードはそのまま出力されません。 代わりに、r の初期値は f(seed, x) になります。 一度に Property に対して1つの初期値しか存在できないので、このふるまいはもっともなものです。

observable.fold(seed, f)scan と似ていますが、最後の値、すなわち、 observable な終了の直前の値だけをエミットします。 Property を返します。

observable.reduce(seed,f)fold の同意語です。

observable.diff(start, f) は Observable の以前の値と現在の値の比較の結果をあらわす Property を返します。 Observable の初期値に関して、以前の値は任意の start になります。

var distance = function (a,b) { return Math.abs(b - a) }
Bacon.sequentially(1, [1,2,3]).diff(0, distance)

上記のコードは結果のストリームにおいて次の要素になります。

1 - 0 = 1
2 - 1 = 1
3 - 2 = 1

observable.zip(other, f) はれとほかのストリームからのイベントと一緒にそろえられた要素をもつ EventStream を返します。 zip ずみのストリームはそれぞれのストリームから値をもつ場合のみ公開され(publish)、単独のストリームが終了するときにのみ値を生み出します。

ストリームのあいだにあまりに多くの「drift」を入れないように注意してください。 ストリームが過剰な値を生み出す場合、zip ずみの observable 内部で過剰なバッファリングが起きます。

Example 1:

var x = Bacon.fromArray([1, 2])
var y = Bacon.fromArray([3, 4])
x.zip(y, function(x, y) { return x + y })

# produces values 4, 6

例 2:

たとえば、同じプロパティによる projections もしくは sampling からペアで同期される observable を 結合させるために zip を使うことができます。一方で、combine で再結合することが起きる二乗処理が避けられます。

var x = obs.map('.x')
var y = obs.map('.y')
x.zip(y, makeComplex)

observable.slidingWindow(max[, min]) は Observable の値の履歴において「sliding window」をあらわす Property を返します。 Property の結果は配列で、オリジナルの observable の最後の n の値が含まれます。 n の最大値は max の引数の値であり、最小値は min 引数の値です。 min の引数が省略された場合、値の小さいほうの制限はありません。

たとえば、1 - 2 - 3 - 4 - 5 というシーケンスの値をもつ s というストリームがあれば、s.slidingWindow(2) のそれぞれの値は [] - [1] - [1,2] - [2,3] - [3,4] - [4,5] になります。s.slidingWindow(2,2) の値は [1,2] - [2,3] - [4,5] になります。

observable.log() は Observable のそれぞれの値をコンソールに記録します。 このメソッドはそれぞれの値と一緒にconsole.log() に渡すオプション引数をとります。 チェーンを成立させるために、オリジナルの Observable を返します。 副作用として、observable は定数のリスナーをもち、ガーベッジコレクトされないことに注意してください。 ですので、このコードはデバッグの用途のみに使い、製品コードから削除してください。例です。

myStream.log("New event in myStream")

or just

myStream.log()

observable.combine(property2, f) は2つの引数をとる関数を使って2つのストリームもしくはプロパティの最新の値を結合します。 scan と似ていて、メソッドの名前を使うことができるので、配列の値をもつ2つのプロパティに対して a.combine(b, ".concat") を行うことができます。 結果は Property です。

observable.withStateMachine(initState, f) は observable の上で状態マシンを実行できます。 初期状態のオブジェクトとそれぞれやってくるイベントを処理し、次の状態を含む配列と出力イベントの配列を返す状態の変換関数を渡します。 コードの例です。ストリームのすべての数値の合計値を計算し、ストリームの終端で値を出力します。

Bacon.fromArray([1,2,3])
  .withStateMachine(0, function(sum, event) {
    if (event.hasValue())
      return [sum + event.value(), []]
    else if (event.isEnd())
      return [undefined, [new Bacon.Next(sum), event]]
    else
      return [sum, [event]]
  })

observable.decode(mapping) は任意のマッピングを使って入力をデコードします。 switch-case もしくは Oracle SQL の decode 関数のようなものです。 たとえば、次の値は 1 の値を「mike」に、2 の値を who プロパティに写します。

property.decode({1 : "mike", 2 : who})

これは実際には combineTemplate にもとづくので、静的なデータと動的なデータをきわめて自由に compose することができます。

property.decode({1 : { type: "mike" }, 2 : { type: "other", whoThen : who }})

decode の戻り値は常に Property です。

EventStream

Bacon.EventStream はイベントのストリームです。次のメソッドをご覧ください。

stream.onValue(f) はイベントストリームへの任意のハンドラーをサブスクライブします。 関数はストリームのそれぞれの値に対して呼び出されます。副作用をストリームに割り当てるもっともシンプルな方法です。 subscribe メソッドとの違いは Event オブジェクトの代わりに、実際のストリームの値を受け取ることです。 下記の関数のコンストラクションルールはここで当てはまります。

stream.onValues(f) は onValue と似ていますが、fへの引数として値(配列を想定します)を分割します。

stream.onEnd(f) はストリームの末端へのコールバックをサブスクライブします。関数はストリームの末端で呼び出されます。

stream.subscribe(f) はイベントストリームへのハンドラー関数をサブスクライブします。 関数は Event オブジェクトを受け取ります (下記を参照)。 subscribe() を呼び指せば、サブスクライブを解除する unsubscribe 関数を返します。 Event への返信としてハンドラー関数からの Bacon.noMore のサブスクライブも解除することができます。

stream.skipDuplicates([isEqual]) は連続して等しい要素を削除します。 [1, 2, 2, 1] は [1, 2, 1] になります。 デフォルトでは同等性のチェックには === 演算子を使います。 isEqual の引数が提供された場合、isEqual(oldValue, newValue) を呼び出すことでチェックします。 たとえば、深い比較を行うためには、stream.skipDuplicates(_.isEqual) のように、 underscore.js から isEqual 関数を使うことができます。

stream1.concat(stream2) は2つのストリームを1つのストリームに連結し、stream1 からその最後までイベントを配信し、 それから stream2 からイベントを配信します。このことが意味するのは stream1 が終了する前に起きた stream2 からのイベントは結果のストリームに含まれないことです。

stream.merge(stream2) は2つのストリームを両方からイベントを配信する1つのストリームにマージします。

stream.skipUntil(stream2)stream2 に Next イベントがあらわれるまで stream からの要素をスキップします。 言い換えると、stream2 における最初のイベントの後で stream からの値を配信することをはじめます。

stream.bufferWithTime(delay) は任意の遅延でストリームイベントをバッファリングします。 バッファリングは任意の遅延で一度フラッシュされます。ですので、入力に [1,2,3,4,5,6,7] が含まれていれば、[1,2,3,4] と [5,6,7] をそれぞれ含む2つのイベントを得ることになります。4 と 5 の数値のあいだでフラッシュが起きます。

stream.bufferWithTime(f) は遅延の代わりに「defer 関数」で動きます。 次のコードは stream.bufferWithTime(10) と同等のシンプルな例です。

stream.bufferWithTime(function(f) { setTimeout(f, 10) })

stream.bufferWithCount(count) は任意のカウントでストリームをバッファリングします。 任意の数の要素が含まれる場合にバッファリングはフラッシュされます。 ですので、count が 2 の場合の [1, 2, 3, 4, 5] のストリームをバッファリングする場合、[1, 2], [3, 4] と [5] の値をもつイベントが出力されます。

stream.bufferWithTimeOrCount(delay, count) はストリームイベントをバッファリングし、バッファリングが任意の数の要素を含むもしくは最後のバッファリングイベントから任意のミリ秒が経過したときにフラッシュを行います。

stream.toProperty() は EventStream にもとづいて Property をつくります。引数を指定しなければ、初期値なしでは Property を得ます。Property はストリームから実際の値を取得し、その後で、つねに現在の値をもちます。

stream.toProperty(initialValue) は任意の初期値によって EventStream をもとに Property をつくります。初期値はストリームから最初の値が来るまで、現在の値として使われます。

stream1.awaiting(stream2) は stream1 が stream2 を待ち構えているかどうかを示す Property をつくります。 すなわち、stream2 から最新の値の後で値を生み出します。AJAX レスポンスを現在待っているかどうかを追跡するのに便利です。

var showAjaxIndicator = ajaxRequest.awaiting(ajaxResponse)

Property

Bacon.Property はリアクティブプロパティです。「現在の値」というコンセプトをもちます。toProperty もしくは scan メソッドのどちからを使うことで EventStream から Property をつくります。Property がどのようにつくられるかによって、初期値をもったりもたないことがないことに注意してください。

Bacon.constant(x) は x という値をもとに定数のプロパティをつくります。

property.subscribe(f) はプロパティへのハンドラー関数をサブスクライブします。 現在の値が存在する場合、Initial イベントが即座にプッシュされます。 更新時には Next イベント、EventStream が終了した場合には、End イベントがプッシュされます。

property.onValue(f) は eventStream.onValue と似ていますが、 プロパティの初期値が存在する場合にプッシュする点が異なります。 このメソッド呼び出しの形式の違いに関して、下記の関数のコンストラクションルールをご参照ください。

property.onValues(f) は onValue と似ていますが、f への関数の引数として値(配列を想定します)を分割します。

property.onEnd(f) はストリームの終端へのコールバックをサブスクライブします。 プロパティのソースストリームが終了するときに関数は呼び出されます。

property.assign(obj, method, [param...]) はこの Property のそれぞれの値でもって任意のオブジェクトのメソッドを呼び出します。

メソッド呼び出しの最初の引数として使われる引数をオプションとして提供することができます。 たとえば、Property を jQuery オブジェクトの「disabled」属性に割り当てたい場合、次のように行うことができます。

myProperty.assign($("#my-button"), "attr", "disabled")

次のよりシンプルな例では Property にもとづいて要素の可視性を切り替えます。

myProperty.assign($("#my-button"), "toggle")

assign メソッドは実際には onValue の同義語であることに注意してください。 下記の関数のコンストラクションルールが当てはまります。

property.sample(interval) は任意のインターバル(ミリ秒単位)でプロパティの値をサンプリングすることで EventStream をつくります。

property.sampledBy(stream) は任意のストリームからのイベントにおいてプロパティの値をサンプリングすることで EventStream をつくります。 結果の EventStream はソースストリームにおいてそれぞれのイベントにおけるプロパティを含みます。

property.sampledBy(property) は任意のプロパティからのそれぞれのイベントにおいてプロパティの値をサンプリングすることで Property をつくります。結果の Property はソースプロパティのそれぞれのイベントにおけるプロパティの値を含みます。

property.sampledBy(streamOrProperty, f) はストリームのイベントにおいてプロパティをサンプリングします。 f(propertyValue, samplerValue) などの任意の関数を使うことで結果の値は形成されます。関数の代わりにメソッドの名前を使うこともできます (「.concat」など)。

property.skipDuplicates([isEqual]) は連続して等しい要素を削除します。 ですので、[1, 2, 2, 1] から [1, 2, 1] が得られます。デフォルトでは同等性のチェックには === 演算子を使います。 isEqual 引数が提供された場合、isEqual(oldValue, newValue) を呼び出すことでチェックします。 このメソッドの古い名前は「distinctUntilChanged」でした。

property.changes() はプロパティの値が変化する EventStream を返します。 初期のイベント以外、プロパティ自身としてまったく同じイベントを返します。 property.changes() は重複する値をスキップしないことにご注意ください。その目的には use .skipDuplicates() を使います。

property.and(other)&& 演算子で複数のプロパティを結合させます。

property.or(other)|| 演算子で複数のプロパティを結合させます。

複数のストリームとプロパティを組み合わせる

Bacon.combineAsArray(streams) は Properties、EventStreams と定数の値を組み合わせるので、結果の Property はすべてのプロパティの値の配列を自身の値としてもちます。入力の配列には Properties と EventStreams の両方を含めることができます。後者の場合、ストリームは最初に Property に変換され、ほかのプロパティと結合されます。

Bacon.combineAsArray(s1, s2, ...) は上記のメソッドと似ていますが、、ストリームを単独の配列として提供する代わりに引数のリストとして提供します。

property = Bacon.constant(1)
stream = Bacon.once(2)
constant = 3
Bacon.combineAsArray(property, stream, constant)
# produces the value [1,2,3]

Bacon.combineWith(f, stream1, stream2 ...) は3つの数値の Properties の現在の合計値を計算するために n-ary 関数の f(v1, v2 ...) を使って n Properties、EventStreams と定数を結合させます。

function sum3(x,y,z) { return x + y + z }
Bacon.combineWith(sum3, p1, p2, p3)

Bacon.combineTemplate(template) はテンプレートオブジェクトを使って Properties、EventStreams と定数値を結合させます。たとえば、passwordusernamefirstnamelastname という名前のストリームもしくはプロパティを得たことを想定します。次のようなことができます。

var password, username, firstname, lastname; // <- properties or streams
var loginInfo = Bacon.combineTemplate({
    magicNumber: 3,
    userid: username,
    passwd: password,
    name: { first: firstname, last: lastname }})

.. 新しい loginInfo プロパティはストリーム/プロパティが新しい値を得るときにテンプレートを使ってこれらすべてのストリームからの値を結合させます。たとえば、次のような値を yield します。

{ magicNumber: 3,
  userid: "juha",
  passwd: "easy",
  name : { first: "juha", last: "paananen" }}

ストリームからのデータを結合することに加え、テンプレートには定数を含めることができます。

すべての Bacon.combine* メソッドは EventStream の代わりに Property を生み出すことにご注意ください。 EventStream としての結果が必要な場合、property.changes() を使うとよいでしょう。

Bacon.combineWith(function(v1,v2) { .. }, stream1, stream2).changes()

Bacon.mergeAll(streams) は EventStreams の配列をマージします。 Bacon.mergeAll(stream1, stream2 ...) は EventStreams をマージします。

Bacon.zipAsArray(streams) はストリームの配列をまとめ、それぞれのソースストリームからの値の配列をそれぞれの値としてもつ新しい EventStream を生成します。

zip はそれぞれのストリームからのイベントはペアとして結合されることを意味し、それぞれのストリームからの最初のイベントが最初に公開されてから、2番目のイベントが公開されます。それぞれのソースストリームからの値が存在すれば、結果はすぐに公開されます。

ストリームのあいだをあまり「drift」 しすぎないように注意してください。 1つのストリームがほかのストリームよりもあまりに多くの値を生み出す場合、zip ずみの observable 内部で過剰なバッファリングが起きます。

例です。

x = Bacon.fromArray([1,2,3])
y = Bacon.fromArray([10, 20, 30])
z = Bacon.fromArray([100, 200, 300])
Bacon.zipAsArray(x, y, z)

# produces values 111, 222, 333

Bacon.zipAsArray(stream1, stream2, ..) は上記のものと似ていますが、単独の配列の代わりに引数のリストをストリームとして提供します。

Bacon.zipWith(streams, f)zipAsArray と似ていますが、戻り値を1つの配列で返す代わりに、n 個のストリームから n 個の値を結合させるために n-ary 関数を使います。

Bacon.zipWith(f, stream1, stream1 ...) は上記のメソッドと似ていますが、単独の配列ではなく、引数のリストとしてストリームを提供します。

Bacon.onValues(a, b [, c...], f) は複数のソース(ストリーム、プロパティ、定数)を配列として結合させ、 値に対して複数作用のある関数を割り当てるための短縮メソッドです。次の例は数値の 3 をログに記録します。

function f(a, b) { console.log(a + b) }
Bacon.onValues(Bacon.constant(1), Bacon.constant(2), f)

関数のコンストラクションのルール

Bacon の多くのメソッドは単独の関数を引数にとります。これらの多くは 関数のコンストラクションのために使う幅広い範囲の異なる引数を受け取ります。

利用可能な異なるフォームを例と一緒に示します。 基本的なフォームは次のようになります。

stream.map(f) は function f(x) を使って値を写します。

基本的なフォームの拡張として、部分関数を使うことができます。

stream.map(f, "bacon") は関数 f(x, y) を使って値を写します。「bacon」が第1引数で、ストリームの値が第2引数です。

stream.map(f, "pow", "smack") は関数 f(x, y, z) を使って値を写します。「pow」と「smack」は最初の2つの引数として、ストリームの値は第3引数として使われます。

それから、メソッド呼び出しを次のようにつくることができます。

stream.onValue(object, method) は指定された名前をもつメソッドを呼び出します。ストリームの値を引数とします。

titleText.onValue($("#title"), "text") は id の「title」をもつ HTML 要素にマッチする jQuery オブジェクトの「text」メソッドを呼び出します。

disableButton.onValue($("#send"), "attr", "disabled") は #send 要素の attr メソッドを呼び出し、「disabled」は第1引数として使われます。 ですのでプロパティが true の値をもつ場合、$("#send").attr("disabled", true) を呼び出します。

メソッドを呼び出すもしくは「property extractor」の構文を使ってフィールドの値を返します。 この構文によって、Bacon はフィールドのタイプをチェックし、本当にメソッドである場合、それを呼び出します。 さもなければ、フィールドの値を返すだけです。例です。

stream.map(".length") はストリームの値の「length」フィールドの値を返します。 配列のストリームに対して意味をなします。ですので、 ["cat", "dog"] に対して 2 を得られます。

stream.map(".stuffs.length") はストリームの値のフィールドである「stuffs」配列の長さを取り出します。 たとえば、{ stuffs : ["thing", "object"] } に対して2を得られます。

stream.map(".dudes.1") は入れ子の「dudes」配列から2番目のオブジェクトを取り出します。たとえば、{ dudes : ["john", "jack"] } に対して「jack」を得られます。

stream.doAction(".preventDefault") はストリームの値の「preventDefault」メソッドを呼び出します。

stream.filter(".attr", "disabled").not() はストリームの値の .attr("disabled") を呼び出し、戻り値でフィルタリングします。 このことは実質的に結果のストリームへの無効な jQuery 要素を含むことを意味します。

上記の内容が当てはまらない場合、Bacon が定数を返します。例です。

mouseClicks.map({ isMouseClick: true }) はすべてのイベントをオブジェクトの { isMouseClick: true } に写します。

関数のコンストラクションをサポートするメソッドは少なくとも onValueonErroronEndmapfilterassigntakeWhilemapErrordoAction を含みます。

Property もしくは EventStream の最新の値

最初の質問で共通するものの1つは「ストリームもしくはプロパティの最新の値の取得方法は?」というものです。 getLatestValue メソッドは存在しませんし、将来導入されることはありません。 ストリーム/プロパティをサブスクライブし、コールバックで値を処理することで値を得ることができます。 複数の値を必要とする場合、combine メソッドの1つを使います。

Bus

Bus は EventStream でストリームに値を push することを可能にします。ほかのストリームを Bus にプラグインすることも可能にします。 Bus は実際にはすべてのプラグインストリームと push メソッドを使ってプッシュされた値をマージします。

new Bacon.Bus() は新しい Bus を返します。

bus.push(x) は任意の値をストリームにプッシュします。

bus.end() はストリームを終わらせます。End イベントをすべてのサブスクライバーに送信します。 このメソッド呼び出しの後で、サブスクライバーにイベントが存在しなくなります。 また、Bus の pushplug メソッドは効果がありません。

bus.error(e) はすべてのサブスクライバーに任意のメッセージとともに Error を送ります。

bus.plug(stream) は Bus にストリームをプラグします。 ストリームからのすべてのイベントは Bus のサブスクライバーに配信されます。 同じストリームのプラグを解除するために使える関数を返します。

plug メソッドによって Bus の生成の後にほかのストリームにおいてマージすることが可能になります。 たとえば、Worzone ゲームで イベントのブロードキャストメカニズムとしてとても有用であることが明らかになっています。

イベント

Bacon.Event はサブクラスの NextEndErrorInitial をもちます。

Bacon.Next は EventStream もしくは Property の次の値です。 ほかのイベントからの Next イベントを区別するには isNext() を呼び出します。

Bacon.End は EventStream もしくは Property の end-of-stream イベントです。 ほかのイベントからの End を区別するには isEnd() を呼び出します。

Bacon.Error は error イベントです。サブスクライバーのなかでこれらのイベントを区別するには isError() を呼び出します。 もしくは error イベントだけに対応するには onError を使います。 errorEvent.error は関連するエラーオブジェクト (通常は文字列) を返します。

Bacon.Initial は Property の初期の(現在の)値です。 ほかのイベントと区別するには isInitial() を呼び出します。 Property のサブスクリプションの後にのみ即座に送信されます。

Event のプロパティとメソッドの一覧です。

event.value() は Next もしくは Initial イベントと関連する値を返します

event.hasValue() は Initial と Next 型のイベントに対して true を返します

event.isNext() は Next イベントに対して true を返します

event.isInitial() は Initial イベントに対して true を返します

event.isEnd() は End イベントに対して true を返します

エラー

Error イベントは常にすべてのストリームコンビネーターを通過します。 ですので、すべての値をフィルタリングした場合でも、error イベントを通過します。 flatMap を使う場合、すべての生成されたストリームと同様に、結果のストリームはソースからの Error イベントを含みます。

observable.onError(f) コールバックを使ってエラーにおいてアクションをとることができます。

observable.errors() は Error イベントだけを含むストリームを返すことができます。 つねに false を返す関数でフィルタリングする場合と同じです。

上記の mapError() 関数もご参照ください。

Error はストリームを終わらせません。observable.endOnError() メソッドは 最初のエラーの後に即座に終了するストリーム/プロパティを返します。

Bacon.js は現在 Error イベント自身を生成しません (Bacon.fromPromise を使ってエラーを変換するとき以外)。 AJAX 呼び出しのような IO ソースから配信されるストリームによって、Error イベントは確実に生成されます。

Join パターン

Join パターンは zip 関数の一般化です。zip は複数のストリームからのイベントを synchronize する一方で、 join パターンはより高度な synchronization パターンの実装を可能にします。 synchronization パターンのリストを結果のイベントストリームに変換するには Bacon.js は Bacon.when 関数を使います。

Bacon.when

不連続なタイムチックでゲームを実装することを考えてみよう。 チックイベントでシンクロナイズされたキーイベントを扱いたいとします。 1つのキーイベントはチックごとに処理します。 チックイベントが存在しなければ、チックをそのまま処理します。

  Bacon.when(
    [tick, keyEvent], function(_, k) { handleKeyEvent(k); handleTick },
    [tick], handleTick)

ここでは順序は重要です。[tick] パターンが最初書かれた場合、これが最初に試され、それぞれのチックで選ばれます。

Join パターンは zip の一般化であり、zip は単一ルールの Join パターンと同等です。 次の observables は同じ出力をもちます。

Bacon.zipWith(a,b,c, combine)
Bacon.when([a,b,c], combine)

Bacon.update

Bacon.when のプロパティバージョンです。 初期の値と「現在」の値をあらわす追加パラメーターをとる関数が必要です。

Bacon.update(
  initial,
  [x,y,z], function(i,x,y,z) { ... },
  [x,y],   function(i,x,y) { ... })

「chemical machine」としての Join パターン

join パターンの直感を素早く得る方法は原子と分子の観点からのアナロジーを通じて理解することです。 join パターンは化学反応のレシピとして見ることができます。 observables の oxygencarbonhydrogen があるとしましょう。 これらのイベントにおいてその種類の「原子」から混合物を生成します。

反応を state することができます。

Bacon.when(
  [oxygen, hydrogen, hydrogen], make_water(),
  [oxygen, carbon],             make_carbon_monoxide(),
)

これで、observables の1つから新しい「原子」が生成されるたびに、この原子は合成物に追加されます。 2つの酸素原子と水素原子があるときに、対応する元素が消費され。make_water を通じて出力が生み出されます。

一酸化炭素を生成する場合に2番目のルールに対して同じセマンティックが適用されます。 ルールは上から下までそれぞれのポイントで試みられます。

Join パターンとプロパティ

Properties は synchronization パターンの一部ではありませんが、代わりにサンプルとして取り上げることができます。 タグの例はたとえば入力フィールドから来た、3つの入力ストリームである $price$quantity$total をとり、pricequantitytotal プロパティにおいて相互に再帰的なふるまいを定義します。

  • updating price sets total to price * quantity
  • updating quantity sets total to price * quantity
  • updating total sets price to total / quantity
  var $price, $total, $quantity = ...
  
  var price = Bacon.when(
    [$price], id,
    [$total, quantity], function(x,y) { return x/y })
   .toProperty(0)  
  
  var total = Bacon.when(
    [$total], id,
    [$price, quantity], function(x,y) { return x*y },
    [price, $quantity], function(x,y) { return x*y })
   .toProperty(0)
   
  var quantity = $quantity.toProperty(1)

Join パターンと Bacon.bus

Join パターンの結果の関数は Bus に値を押しつけることが可能で、それ自身のパターンの1つになることがあります。 たとえば、食事する哲学者の問題の実装は次のように書くことができます。 (http://en.wikipedia.org/wiki/Dining_philosophers_problem)

例:

// Bus を使って実装された利用可能なはし
var chopsticks = [new Bacon.Bus(), new Bacon.Bus(), new Bacon.Bus()]

// hungry は observable の任意のタイプになることができますが、ここでは Bus を使います
var hungry     = [new Bacon.Bus(), new Bacon.Bus(), new Bacon.Bus()]

// 哲学者は1秒ごとに食べ、
// 値を Bus にプッシュして、はしを利用可能にします。
var eat = function(i) {
  return function() {
    setTimeout(function() {
      console.log('done!')
      chopsticks[i].push({})
      chopsticks[(i+1) % 3].push({})
    }, 1000);
    return 'philosopher ' + i + ' eating'
  } 
}

// 両方のはしが利用可能なときに1人の飢えた哲学者が食べられるように
// Bacon.when を使います。
var dining = Bacon.when(
  [hungry[0], chopsticks[0], chopsticks[1]],  eat(0),
  [hungry[1], chopsticks[1], chopsticks[2]],  eat(1),
  [hungry[2], chopsticks[2], chopsticks[0]],  eat(2))
  
dining.log()

// 初期段階においてすべてのはしが利用できるようにします
chopsticks[0].push({}); chopsticks[1].push({}); chopsticks[2].push({})

// 同じ方法で哲学者を飢えた状態にします。この場合、Bus にプッシュするだけです
for (var i = 0; i < 3; i++) {
  hungry[0].push({}); hungry[1].push({}); hungry[2].push({})
} 

Cleaning up

上記で説明したように、次の2つの方法によってサブスクライバーは新しいイベントにおいて関心がなくなったことを発することができます。

  1. ハンドラー関数から Bacon.noMore を返す
  2. subscribe() の呼び出しによって返された dispose() 関数を呼び出す

筆者による RxJs の経験にもとづけば、アプリケーションのコードにある実際の副作用であるサブスクライバーはけっしてこれを行いません。 サブスクライブ解除の責務はたいていの場合、内部に属します。 カスタムのストリーム実装もしくはストリームコンビネーターに取り組んでいないかぎり、無視することができます。 この場合、あなたの成果を Bacon.js に貢献してくださることを歓迎します。

EventStream と Property のセマンティック

EventStream の状態は (t, os) で定義できます。where t は時間であり、os は現在のサブスクライバーのリストです。 この状態はストリームのふるまいを定義します in the sense that

  1. Next イベントがエミットされたとき、同じイベントがすべてのサブスクライバーにエミットされます

  2. イベントがエミットされた後、新しいサブスクライバーが登録された場合でも、再び、エミットされることはありません。 同じ値をもつ新しいイベントはもちろん、後でエミットされます。

  3. 新しいサブスクライバーが登録されたとき、登録した後で、ほかのサブスクライバーとまったく同じイベントを得ます。 このことは、ストリームがサブスクライバーのすべてに「初期の」イベントをエミットしないかぎり、 ストリームは「初期の」イベントを新しいサブスクライバーにエミットできないことを意味します。

  4. End (別の End でも)の後でストリームはほかのイベントをエミットしてはなりません(別 End でも) ルールは長い記述で、異なる観点からの制約を説明します。 EventStream とサブスクライバーのあいだの契約は次のようになります。

  5. それぞれの新しい値に対して、サブスクライバー関数が呼びされます。 新しい値は Next イベントにラップされます。

  6. サブスクライバー関数は Bacon.noMore もしくは Bacon.More のどちらかの結果を返します。undefined の値は Bacon.more のように処理されます。

  7. Bacon.noMore の場合、ソースはサブスクライバーを再び呼び出してはなりません。

  8. ストリームが終了するとき、サブスクライバー関数は End イベントで呼び出されます。 この場合、サブスクライブ関数の戻り値は無視されます。

A PropertyEventStream と同じようにふるまいます。違いは次のとおりです。

  1. subscribe の呼び出しにおいて、(存在すれば)現在の値を配信します。Initial イベントにつつまれた サブスクライバー関数に現在の値を提供します
  2. このことは Property が以前 x の値をサブスクライバーにエミットしていて、それがエミットされた最新の値の場合、 この値を新しいサブスクライバーに配信することを意味します。
  3. Property は最初から値をもっていてももっていなくても構いません。Property がどのようにつくられたのか次第です。

アトミックな更新

バージョン 0.4.0 から、Bacon.js はプロパティにアトミックアップデートのサポートを追加しました。制限事項があります。

プロパティ A と B とプロパティ C = A + B がある場合を考えてみましょう。 A と B の両方が D に依存する場合を想定します。D が変化したとき、A と B の両方が変化します。

D が d1 -> d2 を変更する場合、a1 -> a2 の値と B は b1 -> b2 を同時に変更するので、a1+b1 -> a2+b2 へ直接遷移するように、C をアトミックに更新したいとします。 そして、実際、実際にそれを行います、0.4.0 以前のバージョンでは、C には a1+b1 -> a2+b1 -> a2+b2 のような 追加の遷移状態がありました。

アトミックな更新は Properties に限定されます。このことが意味するのは、 EventStreams において同時並行のイベントは Properties への追加の遷移状態を引き起こす可能性があるということです。 ただし、Properties だけを組み合わせているかぎり、更新はアトミックになります。

RxJs ユーザーの方へ

Bacon.js は RxJs ととてもよく似ているので、ピックアップすることはとてもかんたんです。 Bacon 側における大きな違いは、2種類の区別できる Observable である EventStream と Property の存在です。前者は離散的なイベントに対するものであり、後者は「現在の値」のコンセプトをもつ observable なプロパティに対するものです。

「cold observables」は存在しません、すなわち、すべての EventStreams と Properties は サブスクライバーのあいだで首尾一貫していることを意味します: イベントが起きたとき、すべてのサブスクライバーは同じイベントをオブサーブするからです。 RxJs の経験があれば、cold observables と scan と startWith を使ってコンストラクトされたストリームからの一貫性のない出力に関連する嫌な問題に遭遇したことがおそらくあるでしょう。これらは Bacon.js では起きません。

エラーハンドリングも少し異なります。Error イベントはストリームを終了させることはありません。ですので、ストリームは複数のエラーをもつことがあります。筆者にとって、エラーのときに常にストリームを終了させるよりも有意義です。この方法では、アプリケーション開発者はエラーハンドリングを超えるより直接的なコントロールをもつことになります。エラーで終わるストリームを得るために stream.endOnError() を常に使うことができます!

をご覧ください。

仕様 をご覧ください。

Worzone のデモソース をご覧ください。

インストール

CoffeeScript のソースをコンパイルしてテストを実行するために必要な依存ライブラリをインストールするために npm を使います。ですので最初に次のコマンドを実行します。

npm install

ビルド

CoffeeScript のソースをビルドして JavaScript を生成します。

grunt

結果の JavaScript ファイルは dist ディレクトリに生成されます。

テスト

ユニットテストを実行します。

npm test

ブラウザーのテストを実行します。

npm install
npm install --save-dev browserify@1.18.0
npm install -g testem
testem

パフォーマンステストを実行します。

coffee performance/*

依存関係

実行時: jQuery もしくは Zepto.js (オプション: jQ/Zepto バインディング) ビルド/テスト: node.js、npm、coffeescript

ほかのライブラリとの互換性

Bacon.js はプロトタイプやグローバルオブジェクトでごちゃごちゃにしません。次の例外があります。

  • Bacon オブジェクトをエクスポートします。ブラウザーにおいて、これは window オブジェクトに追加されます。
  • jQuery が定義されていれば、asEventStream メソッドを jQuery に追加します (Zepto も同じです)

ですので、互換性があり、お行儀がよい (nice citizen) です。

たとえば Array プロトタイプに何かを追加するほかのライブラリに関して、Bacon がどのように動くのかわかりません。テストを追加したほうがよいのかもしれません。

ブラウザーとの互換性

TLDR: よい

Bacon.js は UI ライブラリなのでブラウザーに依存していません。

作者個人は Bacon.js を Chrome、Firefox、Safari、IE 6+、iPhone、iPad と一緒に使っています。

それぞれのコミットごとにモダンブラウザーと IE6+ で自動テストが行われます。

Bacon.js のフルテストスイートは広範囲のブラウザーと testling.ci のもとで実行されます。

ブラウザーサポートのテストレポート

これらのテストの結果はあまり信用できず、ランダムな失敗を生み出しますが、ボトムラインにおいて互換性の問題が存在しないということです。

Node.js

もちろんです。動きます。試してください。

npm install baconjs

node を入力し、次のコードを試してください。

Bacon = require("baconjs").Bacon
Bacon.sequentially(1000, ["B", "A", "C", "O", "N"]).log()

AMD

もちろん。AMD を通して Bacon をエクスポートし、後方互換性のために window に割り当てます。

Bacon に加えて jQuery と AMD を一緒に使うのであれば、モジュールのロードが問題にならないように、 jQuery にモンキーパッチが必要です。

define(function (require) {
    var $ = require('jquery'),
        Bacon = require('Bacon');

    $.fn.asEventStream = Bacon.$.asEventStream;

    $(document).asEventStream('click').onValue(function (e) {
        console.log(e.clientX + ', ' + e.clientY);
    });
});

なぜ Bacon?

RxJs もしくはほかのライブラリを使わないのか?

  • 「ほかの選択肢」がないから
  • bacon をオープンソースにしたいから
  • bacon の充実したドキュメントがほしいから
  • Observable の抽象化がじゅうぶんではないからと考えているから。ふるまい (hot/cold observable など)のバリアントの余地がたくさんありすぎるからです。EventStream と Property のほうが快適に感じます。
  • Bacon に自動テストが必要だからです。これらもドキュメントになります。
  • Array prototype のコードを散らかしたくないから

貢献

GitHub の issues と Pull リクエストをお使いください。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment