What I Like About Bacon.js

These are notes from a class I taught on bacon.js

Event Listeners become Event Streams

Wrap your events in bacon! This common pattern

$("#clickme").on('click', function(event){ alert(event.target)})

becomes

var clicked = $("#btn").asEventStream('click');
clicked.onValue(function(event){ alert(event.target) });

You can give asEventStream the same arguments as jQuery.on.

$("div").asEventStream("click", ".specific-selector",function(e){})

Promises

Bacon can also create a stream by wrapping a promise object.

Bacon.fromPromise($.ajax({ url : "/sup.json"})) ;

or an event

Bacon.fromEvent(document.body, "click");

Properties

Bacon provides an abstraction for the common pattern in which you want to update a value asynchronously. It does this by allowing streams to become Properties, which are like streams but take the last value passed as the current value.

stream.toProperty()

You can give .toPoperty a starting value until data gets sent down the stream.

stream.toProperty(1)

This will contain the default value of true until it changes, then keep the the latest value.

var checked = $("#checkbox").asEventStream('click')
			.map(function(e){ return e.target.value })
			.toProperty(true);

Observables

EventStreams and Properties are both “Observables” and share some methods that also return observables for chaining.

This is really cool because of the power this abstraction affords. You can now manipulate events as they travel through your code using all those functional tools you love.

$("#input1").asEventStream("keyup")
.map(function(event) { return $(event.target).val() })

You may be familiar with group, flatMap, join, reduce, combine, transform, skip, filter, take, merge, first, transform.

See http://www.cheatography.com/proloser/cheat-sheets/bacon-js/ for a complete list with descriptions.

Defining your own event sources

You can define your sources using Bacon.fromBinder. It takes a function that gets passed a function to send events and returns a function that is called to unsubscribe from the event stream.

var event = Bacon.Next("This is a way to create a Bacon event");

var myStream = Bacon.fromBinder(function(send){
	send(1); // can send values
	send(event); // can take an event object
	send(Bacon.Error("fail")); // or an error
	send(new Bacon.End()); // returning Bacon.End indicates the stream is over
});


  var rsvps = Bacon.fromBinder(function(sink) {
    must.Rsvps(sink);
    return $.noop;
  });

Repeat

Repeat is interesting for asynchronous patterns.

From the docs

Bacon.repeat(fn) Calls generator function which is expected to return an observable. The returned EventStream contains values and errors from the spawned observable. When the spawned observable ends, the generator is called again to spawn a new observable.

This is repeated until the generator returns a falsy value (such as undefined or false).

We can use this for a long-polling application where we want to keep a local mirror of a remote resource.

var ajaxParams = { url: "/resource.json", timeout: 1000000 }

var localResource = Bacon.repeat(function(){
	return Bacon.fromPromise($.ajax(ajaxParams)));
}).toProperty();

Warning: Lazy Evaluation

LE is a good thing because it can minimize the amount of iteration necessary, but it can lead to traps. Remember that functions are not evaluated when they’re called like in _ , they are evaluated when necessary, so try to not use mutable variables from another scope in a closure.

Function construction

Bacon has some jazz that saves you from always having to create functions manually for certain tasks.

At least onValue, onError, onEnd, doAction, map, filter, assign, takeWhile, mapError support function construction.

You can pass a handler an object and a method name.

Bacon.fromPromise($.get('/name.txt'))
	.toProperty("loading")
	.onValue($("#name"), 'text');

That’s equivalent to

Bacon.fromPromise($.get('/name.txt'))
	.toProperty("loading")
	.onValue(function(val){
		$("#name").text(val);
	})

Or

var name = $('#name');
Bacon.fromPromise($.get('/name.txt'))
	.toProperty("loading")
	.onValue(name.text.bind(name))

You can also access attributes declarativly.

stream.map('.box.1')
// is the same as
stream.map(function(val){val.box[1]});
// you can map things to constants

Currying

When setting a handler, the remaining arguments gets passed to the function.

stream.map(function(text, value){ return text+value; }, "hello");

Buses

These are special EventStreams with a push method that adds events to the stream. They can also be composed with other EventStreams.

var bus = Bacon.Bus(); // creates bus
bus.push(1); // add value to stram
bus.end() // ends the stream
bus.error(error) // sends an error to all subscribers
bus.plug(stream) // funnels an event stream into the bus

Merging

Lastly, event streams can me merged. This is really useful.

bus.merge(eventStream)