
A functional reactive programming lib for TypeScript JavaScript, written in TypeScript.
Turns your event spaghetti into clean and declarative feng shui bacon, by switching
from imperative to functional. It's like replacing nested for-loops with functional programming
concepts like map and filter. Stop working on individual events and work with event streams instead.
Combine your data with merge and combine.
Then switch to the heavier weapons and wield flatMap and combineTemplate like a boss.
Here's the stuff.
Bacon.js starting from version 3.0 is a Typescript library so you won't need any external types. Just
Install using npm.
npm install baconjs
Then you can
import { EventStream, once } from "baconjs"
let s: EventStream<string> = once("hello")
s.log()
As you can see, the global methods, such as once are imported separately.
Check out the new API Documentation, that's now generated using Typedoc from the Typescript source code.
You can directly import Bacon.js as single aggregated ES6 module.
import * as Bacon from 'node_modules/baconjs/dist/Bacon.mjs';
Bacon.once("hello").log();
If you're on to CommonJS (node.js, webpack or similar) you can install Bacon using npm.
npm install baconjs
Try it like this:
node
Bacon=require("baconjs")
Bacon.once("hello").log()
The global methods, such as once are available in the Bacon object.
For bower users:
bower install bacon
Both minified and unminified versions available on cdnjs.
So you can also include Bacon.js using
<script src="https://cdnjs.cloudflare.com/ajax/libs/bacon.js/2.0.9/Bacon.js"></script>
<script>
Bacon.once("hello").log()
</script>
Bacon.js is an UMD module so it should work with AMD/require.js too. Not tested lately though.
Prefer to drink from the firehose? Download from Github master.
The idea of Functional Reactive Programming is quite well described by Conal Elliot at Stack Overflow.
Bacon.js is a library for functional reactive programming. Or let's say it's a library for working with events in EventStreams and dynamic values (which are called Properties in Bacon.js).
You can wrap an event source, say "mouse clicks on a DOM element" into an EventStream by saying
let $ = (selector) => document.querySelector(selector)
var clickE = Bacon.fromEvent($("h1"), "click")
The $ helper function above could be replaced with, for instance, jQuery or Zepto.
Each EventStream represents a stream of events. It is an Observable, meaning
that you can listen to events in the stream using, for instance, the onValue method
with a callback. Like this:
clickE.onValue(() => alert("you clicked the h1 element") )
But you can do neater stuff too. The Bacon of Bacon.js is that you can transform,
filter and combine these streams in a multitude of ways (see EventStream API). The methods map,
filter, for example, are similar to same functions in functional list programming
(like Underscore). So, if you say
let plusE = Bacon.fromEvent($("#plus"), "click").map(1)
let minusE = Bacon.fromEvent($("#minus"), "click").map(-1)
let bothE = plusE.merge(minusE)
.. you'll have a stream that will output the number 1 when the "plus" button is clicked
and another stream outputting -1 when the "minus" button is clicked. The bothE stream will
be a merged stream containing events from both the plus and minus streams. This allows
you to subscribe to both streams with one handler:
bothE.onValue(val => { /* val will be 1 or -1 */ console.log(val) })
Note that you can also use the log method to log stream values to console:
bothE.log()
In addition to EventStreams, bacon.js has a thing called Property, that is almost like an
EventStream, but has a "current value". So things that change and have a current state are
Properties, while things that consist of discrete events are EventStreams. You could think
mouse clicks as an EventStream and mouse cursor position as a Property. You can create Properties from
an EventStream with scan or toProperty methods. So, let's say
let add = (x, y) => x + y
let counterP = bothE.scan(0, add)
counterP.onValue(sum => $("#sum").textContent = sum )
The counterP property will contain the sum of the values in the bothE stream, so it's practically
a counter that can be increased and decreased using the plus and minus buttons. The scan method
was used here to calculate the "current sum" of events in the bothE stream, by giving a "seed value"
0 and an "accumulator function" add. The scan method creates a property that starts with the given
seed value and on each event in the source stream applies the accumulator function to the current
property value and the new value from the stream.
Hiding and showing the result div depending on the content of the property value is equally straightforward
let hiddenIfZero = value => value == 0 ? "hidden" : "visible"
counterP.map(hiddenIfZero)
.onValue(visibility => { $("#sum").style.visibility = visibility })
For an actual (though a bit outdated) tutorial, please check out my blog posts
There's a multitude of methods for creating an EventStream from different sources, including the DOM, node callbacks and promises for example. See EventStream documentation.
Properties are usually created based on EventStreams. Some common ways are introduced in Property documentation.
You can combine the latest value from multple sources using combine, combineAsArray, combineWith or combineTemplate.
You can merge multiple streams into one using merge or mergeAll.
You can concat streams using concat or concatAll.
If you want to get the value of an observable but emit only when another stream emits an event, you might want to use sampledBy or its cousin withLatestFrom.
One of the common first questions people ask is "how do I get the latest value of a stream or a property". There is no getLatestValue method available and will not be either. You get the value by subscribing to the stream/property and handling the values in your callback. If you need the value of more than one source, use one of the combine methods.
Bus is an EventStream that allows you to push values into the stream.
It also allows plugging other streams into the Bus.
There are essentially three kinds of Events that are emitted by EventStreams and Properties:
map, filter and most of the other operators
also deal with values only.If you want to subscribe to all events from an Observable, you can use the subscribe method.
Error events are always passed through all stream operators. So, even
if you filter all values out, the error events will pass through. If you
use flatMap, the result stream will contain Error events from the source
as well as all the spawned stream.
You can take action on errors by using onError.
See also mapError, errors, skipErrors,
Bacon.retry and flatMapError.
In case you want to convert (some) value events into Error events, you may use flatMap like this:
stream = Bacon.fromArray([1,2,3,4]).flatMap(function(x) {
if (x > 2)
return new Bacon.Error("too big")
else
return x
})
Conversely, if you want to convert some Error events into value events, you may use flatMapError:
myStream.flatMapError(function(error) {
return isNonCriticalError(error) ? handleNonCriticalError(error) : new Bacon.Error(error)
})
Note also that Bacon.js operators do not catch errors that are thrown.
Especially map doesn't do so. If you want to map things
and wrap caught errors into Error events, you can do the following:
```js wrapped = source.flatMap(Bacon.try(
$ claude mcp add bacon.js \
-- python -m otcore.mcp_server <graph>