Skip to content
Juha Paananen edited this page Sep 11, 2020 · 37 revisions

Why the name?

Well, I stumbled upon reactive-banana when researching functional reactive programming. When I started working on my own library in Haskell, I also decided on a food-themed name and Bacon was the first food that I came up with. Hence, reactive-bacon.

Then I realized that this could be useful in a Javascript context and some of my actual work and made a Javascript version, called Bacon.js. Using the suffix was fashionable back then I figured.

Later on, someone came up with a more sophisticated story: the library is named after Sir Francis Bacon, the father of the Scientific Method.

How do I get the latest value of a Property?

There is no getLatestValue method available and will not be either. You get the value by subscribing to the stream/property (using onValue) and handling the values in your callback. If you need the value of more than one source, use one of the combine methods.

Bacon.js works so that Properties and EventStreams connect to their sources (such as jQuery events) when they have at least one listener. They also automatically disconnect when there are no listeners. So, subscribing to the stream or property is important also from this aspect.

Why isn't my Property updated?

You have a situation like this:

var bus = new Bacon.Bus;
var property = bus.toProperty('FIRST');
bus.push('SECOND');
property.onValue(function(val) {
  console.log(val);
});

You'd like the console to log 'SECOND' but it logs 'FIRST' instead.

What's happening here is that your Property won't get updated when there are no listeners. Before you add an actual listener using onValue, the Property is not active; it's not listening to the underlying Bus. It ignores input until someone's interested. So it all boils down to the "laziness" of all Bacon streams and properties. This is a key feature too: the streams automatically "plug in" to their sources when a subscriber is added, and "unplug" when there are no more subscribers.

You may consider using a Bacon.Model instead. That'll give you a "settable property" with a bunch of additional features.

Why isn't my subscriber called?

There's a catch in using Bacon.fromArray (as well as Bacon.once and other synchronously responding sources): if you attach multiple subscribers, it will spit all of its contents to the first one and the latter subscribers will never be called. So, if you do for example this:

$(function() {
  var stream = Bacon.fromArray([1,2,new Bacon.Error("oops")])
  stream.onValue(function(x) {
      console.log("value", x)
  })
  stream.onError(function(x) {
      console.log("error", x)
  })
});

... the errors won't be logged. In real applications, you probably won't be using fromArray sources this way, but if you just want a workaround, you can change fromArray(x) to sequentially(0, x) to get an asynchronous source that'll handle multiple subscribers properly.

You wrap any stream as asynchronous by using stream.delay(0) too, but here's the catch that Error events aren't delayed.

Check you this fiddle and this issue for details.

Does bacon.js support synchronized/atomic updates of Properties?

Yes! Added in v0.4.0. See Readme for more details.

Assume you have properties A and B and a property C = A + B. When the value of A changes from a1 to a2 and B changes from b1 to b2 simultaneously, you'd like C to update atomically so that it would go straight from a1+b1 to a2+b2. And it indeed does, starting from 0.4.0.

How does FRP / Bacon.js work on large scale?

So far my impression is that you can use FRP on both the small scale and the large, but that it is not a magic bullet for solving your problems. You'll still have to think about application architecture or you'll run into similar problems (spaghetti code) as with other paradigms.

You might consider applying an MVC-style architecture to your application and use Bacon.js streams and properties for communication between components. Your Models may be Streams, Properties or more complex objects exposing Streams, Properties and Buses. Have a look at one of my blog posts on TodoMVC with Bacon.js for some more information on this.

What do I lose when I use FRP? What are the cons?

It's up to you. You may apply FRP to solve some isolated problems in your application and lose nothing. Or you may model your application in a new way. If you, for instance, ditch MVF frameworks and use Bacon+jQuery instead, you'll get a somewhat simpler solution, less indirection and better composability. Then again, you'll lose the specific benefits of the framework. For instance, Backbone Collections are great and if you master them, you don't necessarily want to lose them. The good news is that you don't have to throw good things away. Bacon.js is not a framework, so it plays ball with frameworks. Have a look at how @pyykkis combined Bacon with Backbone collections. Please use any combination you like and tell me how you did it and how did you succeed :)

How stable is it? Is it ready for production?

It's been used in production for years. Lots of bugs have been fixed with the help of the open source community on the way.

Isn't it slow? How is the performance?

Depends. In most cases, Bacon.js will not be a deciding factor in the performance of your application. There is, however, some overhead in the Bacon.js event-dispatching system (the system that takes care that your events are delivered correctly when and where they're supposed to be sent). So it'll do just fine when you create Bacon events from mouse events, ajax requests, Socket.IO messages or timers that occur 100 times a second. But it won't be so great for delivering audio samples as events at 44.1 kHz.

Performance benchmarks have shown that Kefir and RxJs have significantly less overhead. The main reason for the higher overhead in Bacon.js is the glitch-free event delivery system.

How is the learning curve of Bacon.js?

Stepping into FRP from imperative programming is a big change, and will definitely take some time to grasp. However, if you're familiar with FP list handling (map, filter, flatMap...), you'll find similarities. I've had a couple of hands-on Bacon.js workshops so far and every single participant was able to complete at least a couple of features on their own (well, I gave some hints for sure).

Try it out and tell me.

How do I integrate Bacon.js with X?

Currently, Bacon.js supports creating EventStreams from jQuery events, DOM EventTarget, Node.js EventEmitter, jQuery Deferred, or any polling function. Have a look at the readme.

If you need to plug in other sources, creating an EventStream yourself isn't actually very hard. Have a look at the source code where all of these converters are implemented for reference. Also, read my blog post.

One option is always to create a Bus and just push events into that:

var bus = new Bacon.Bus()
bus.push("event")

How about TypeScript?

Starting from version 3.0 Bacon.js has been written in Typescript. This improves code quality and also guarantees that the typings for Typescript are correct. So, you don't need @types/baconjs anymore.

How can I integrate bacon to my (coffeescript/haxe/dart/etc) code?

Just like you'd use any other Javascript library.

Bacon.js is a CoffeeScript library that's build into Javascript using Grunt. You can use one of the built js files as you like, or you can use it as a dependency in your Node.js, Yeoman or Bower project. See readme.

What's the difference to RxJs?

Bacon.js is inspired by RxJs and has similar concepts. The main difference in the design is the existence of two flavors of Observables: EventStream and Property, each of which have clearly defined semantics. The RxJs Observable does not tie the semantics as tightly. For instance, in RxJs there are "hot" and "cold" observables that behave differently even though the expose the same Observable interface.

Bacon.js also has glitch-free updates and lazy evaluation of event values.

Doesn't this turn "callback" hell into "mapfiltercombine" hell? How I will integrate a new developer to my FRP code?

What FRP does to callback hell is the same thing that FP does to for-loops. Some may argue that for-loops are easier to read than a map-filter-combo. It's the same thing with all new tools. Some managers fear that their employees will never learn Scala, so everything has to be written in Java. Would you still like to write code in Cobol?

What if I have multiple Frames in my application?

Bacon.js used instanceof checks and has some internal state that relies on the assumption that there's only one instance of the "Bacon machine".

Use a single instance of Bacon and share it between your frames. Like window.Bacon = window.parent.Bacon. See related issue

Is jQuery required?

Nope. Bacon.js adds an asEventStream to JQuery objects if it finds JQuery. But you can certainly use it without jQuery. You can use Bacon.fromEvent, Bacon.from(Node)Callback and Bacon.fromBinder to wrap any event sources in Bacon.

What does "lazy evaluation" mean in Bacon.js?

There's actually two kinds of "laziness" involved in Bacon.js:

  1. When an Observable has zero subscribers, it will not connect to its data source. As a result, nothing will happen unless you add at least one subscriber. Subscribers are added using onValue/ forEach. The log method also adds a subscriber.

  2. Some methods, such as map and combine* also evaluate the event value lazily. This means that the function that you give to map as an argument won't be called unless the value is needed. This brings performance benefits in some cases such as when you combine a bunch of values from different sources using Bacon.combineWith but you need only some of the values, which you'll extract using sampledBy. In this case, the function given to combineWith will only be called for the values that are actually extracted using sampledBy.

For comparison, the first kind of laziness is implemented in other libraries too (rxjs, kefir at least), while the second kind is (afaik) only implemented in Bacon.js.