Bacon.js for dummies

Bacon.js is an FRP module for events on javascript. Which can transform
your event listener/handler to a functional event stream. After servey a few blogs and example project,
I found it is a really interesting concept and can make event handling speghetti code into clear functional logics.

Event stream

First, what is event stream?
Actually it is nothing special, it is just an event listener that listen to specific event.

For example,

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

Can transfer to event stream by Bacon.js’s asEventStream helper:

clicked = $("#clickme").asEventStream('click')

And add handler to event stream, listen to click event:

clicked.onValue(function(event){ alert(event.target) })

So what’s the different?

Remember what I said in the beginning, event stream is functional.
Which means it provide functional interface to manipulate events:

clicked
  .map(function(event) { return event.target })
  .onValue(function(element) { alert(element) })
// will map the event to event.target

clicked.skip(1).take(4).onValue(function(event) { alert(event.target) })
// will only take the 2-5 click event.

clicked
  .filter(function(event) { return event.type == 'click' })
  .onValue(function(event) { alert(event.target)})
// will only take 'click' event on event stream

Merge

A powerful feature of event stream is it can merge with multiple stream.
For example, if we want to listen 2 click event with enable and disable state, you can merge the stream:

enable = $('#enable').asEventStream('click').map(true)
disable = $('#disable').asEventStream('click').map(false)
enable.merge(disable).onValue(function(state) { alert(state) })

Property

Moreover, it provide property: an event stream with state.
What property different with event stream is it will remember the state of stream,
which is the event object or mapped value.

buttonState = enable.merge(disable).toProperty(false)
// with initial state false
buttonState.onValue(function(state) {
  $('#button').toggleClass('enable', state)
})

Also, it provide scan and assign helper to provide advance usage.

Message Queue

We can also use the event stream as message queue.

messageQueue = new Bacon.Bus()
messageQueue.plug(enable.map({type: 'enable'}))
messageQueue.plug(disable.map({type: 'disable'}))
//plug event stream to queue

messageQueue.onValue(function(event){ alert(event.type)})
// listen and alert event state

messageQueue.push({ type: 'disable'})
// push event manually, alert event

The project worzone provide a more detailed implementation example for messageQueue

Ajax

You can use the .ajax() helper to pass stream params to .ajax(params) and listen the promise object as event stream

response = enable.map({url: '/enable', method: 'post' }).ajax()
response.onValue(function(data) {
  alert(data)
})

So what is it trying to solve?

FRP, functional reactive programming on events.
Which reduce the duplicated part of event handling, and make code more looks like pure logic and functions.

I recommand to read the example of Bacon.js tutorial,
and todomvc example with Bacon.js.
It shows how the functional declarative can simplify codes.

Jimchao

A developer, hacker, traveler and boarder live in New York City. You can follow my code at github.com/rafe

Comments