API
+ + +Creating streams
+ + +
+$.asEventStream(eventName)
creates an EventStream from events on a
+jQuery or Zepto.js object. You can pass optional arguments to add a
+jQuery live selector and/or a function that processes the jQuery
+event and its parameters, if given, like this:
+
+
+
+Bacon.fromPromise(promise [, abort] [, eventTransformer])
creates an EventStream from a Promise object such as JQuery Ajax.
+This stream will contain a single value or an error, followed immediately by stream end.
+You can use the optional abort flag (i.e. ´fromPromise(p, true)´ to have the abort
method of the given promise be called when all subscribers have been removed from the created stream.
+You can also pass an optional function that transforms the promise value into Events. The default is to transform the value into [new Bacon.Next(value), new Bacon.End()]
.
+Check out this example.
+Bacon.fromEvent(target, eventName [, eventTransformer])
creates an EventStream from events
+on a DOM EventTarget or Node.JS EventEmitter object, or an object that supports event listeners using on
/off
methods.
+You can also pass an optional function that transforms the emitted
+events' parameters.
+
+
+
+Bacon.fromCallback(f [, args...])
creates an EventStream from a function that
+accepts a callback. The function is supposed to call its callback just
+once. For example:
+This would create a stream that outputs a single value "Bacon!" and ends +after that. The use of setTimeout causes the value to be delayed by 1 +second.
+You can also give any number of arguments to fromCallback
, which will be
+passed to the function. These arguments can be simple variables, Bacon
+EventStreams or Properties. For example the following will output "Bacon rules":
+
+
+
+Bacon.fromCallback(object, methodName [, args...])
a variant of fromCallback which calls the named method of a given object.
+Bacon.fromNodeCallback(f [, args...])
behaves the same way as Bacon.fromCallback
,
+except that it expects the callback to be called in the Node.js convention:
+callback(error, data)
, where error is null if everything is fine. For example:
+
+
+
+Bacon.fromESObservable(observable)
creates an EventStream from an
+ES Observable. Input can be any
+ES Observable implementation including RxJS and Kefir.
+Bacon.fromNodeCallback(object, methodName [, args...])
a variant of fromNodeCallback which calls the named method of a given object.
+Bacon.fromPoll(interval, f)
polls given function with given interval.
+Function should return Events: either Bacon.Next
or Bacon.End
. Polling occurs only
+when there are subscribers to the stream. Polling ends permanently when
+f
returns Bacon.End
.
+Bacon.once(value)
creates an EventStream that delivers the given
+single value for the first subscriber. The stream will end immediately
+after this value. You can also send an Bacon.Error
event instead of a
+value: Bacon.once(new Bacon.Error("fail"))
.
+Bacon.fromArray(values)
creates an EventStream that delivers the given
+series of values (given as array) to the first subscriber. The stream ends after these
+values have been delivered. You can also send Bacon.Error
events, or
+any combination of pure values and error events like this:
+`Bacon.fromArray([1, new Bacon.Error()])
+Bacon.interval(interval, value)
repeats the single element
+indefinitely with the given interval (in milliseconds)
+Bacon.sequentially(interval, values)
creates a stream containing given
+values (given as array). Delivered with given interval in milliseconds.
+Bacon.repeatedly(interval, values)
repeats given elements indefinitely
+with given interval in milliseconds. For example, repeatedly(10, [1,2,3])
+would lead to 1,2,3,1,2,3...
to be repeated indefinitely.
+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
).
The generator function is called with one argument — iteration number starting from 0
.
Here's an example:
+
+The example will produce values 0, 1 and 2.
+ + +
+Bacon.never()
creates an EventStream that immediately ends.
+Bacon.later(delay, value)
creates a single-element stream that
+produces given value after given delay (milliseconds).
+new Bacon.EventStream(subscribe)
creates an EventStream
with the given subscribe function.
property.changes
creates a stream of changes to the Property
. The stream does not include
+an event for the current value of the Property at the time this method was called.
+property.toEventStream()
creates an EventStream based on this Property. The stream contains also an event for the current
+value of this Property at the time this method was called.
new Bacon.Bus()
creates a pushable/pluggable stream (see Bus section below)
Pro tip: you can also put Errors into streams created with the
+constructors above, by using an Bacon.Error
object instead of a plain
+value.
Bacon.fromBinder for custom streams
+ + +If none of the factory methods above apply, you may of course roll your own EventStream by using Bacon.fromBinder
.
+Bacon.fromBinder(subscribe)
The parameter subscribe
is a function that accepts a sink
which is a function that your subscribe
function can "push" events to.
For example:
+
+As shown in the example, you can push
+-
+
- A plain value, like
"first value"
+ - An
Event
object includingBacon.Error
(wraps an error) andBacon.End
(indicates +stream end).
+ - An array of event objects at once +
Other examples can be found on JSFiddle and the +Bacon.js blog.
+The subscribe
function must return a function. Let's call that function
+unsubscribe
. The returned function can be used by the subscriber (directly or indirectly) to
+unsubscribe from the EventStream. It should release all resources that the subscribe function reserved.
The sink
function may return Bacon.noMore
(as well as Bacon.more
+or any other value). If it returns Bacon.noMore
, no further events will be consumed
+by the subscriber. The subscribe
function may choose to clean up all resources at this point (e.g.,
+by calling unsubscribe
). This is usually not necessary, because further calls to sink
are ignored,
+but doing so can increase performance in rare cases.
The EventStream will wrap your subscribe
function so that it will
+only be called when the first stream listener is added, and the unsubscribe
+function is called only after the last listener has been removed.
+The subscribe-unsubscribe cycle may of course be repeated indefinitely,
+so prepare for multiple calls to the subscribe function.
A note about the new Bacon.Next(..)
constructor: You can use it like
+But the canonical way would be
+
+The former version is safe only when you know that the actual value in +the stream is not a function.
+The idea in using a function instead of a plain value is that the internals on Bacon.js take
+advantage of lazy evaluation by deferring the evaluations of values
+created by map
, combine
.
+Bacon.noMore
The opaque value sink
function may return. See Bacon.fromBinder
.
+Bacon.more
The opaque value sink
function may return. See Bacon.fromBinder
.
Common methods in EventStreams and Properties
+ + +Both EventStream and Property share the Observable interface, and hence share a lot of methods. +Methods typically return observables so that methods can be chained; exceptions are noted. +Common methods are listed below.
+ + + +
+observable.subscribe(f)
subscribes given handler function to event stream. Function will receive event objects
+for all new value, end and error events in the stream.
+The subscribe() call returns a unsubscribe
function that you can call to unsubscribe.
+You can also unsubscribe by returning Bacon.noMore
from the handler function as a reply
+to an Event.
+stream.subscribe
and property.subscribe
behave similarly, except that the latter also
+pushes the initial value of the property, in case there is one.
+observable.onValue(f)
subscribes a given handler function to the observable. Function will be called for each new value.
+This is the simplest way to assign a side-effect to an observable. The difference
+to the subscribe
method is that the actual stream values are
+received, instead of Event
objects.
+The Function Construction rules below apply here.
+Just like subscribe
, this method returns a function for unsubscribing.
+stream.onValue
and property.onValue
behave similarly, except that the latter also
+pushes the initial value of the property, in case there is one.
+observable.onValues(f)
like onValue
, but splits the value (assuming its an
+array) as function arguments to f
.
+observable.onError(f)
subscribes a callback to error events. The function will be called for each error in the stream.
+Just like subscribe
, this method returns a function for unsubscribing.
+observable.onEnd(f)
subscribes a callback to stream end. The function will be called when the stream ends.
+Just like subscribe
, this method returns a function for unsubscribing.
+observable.toPromise([PromiseCtr])
returns a Promise which will be resolved with the last event coming from an Observable.
+The global ES6 promise implementation will be used unless a promise constructor is given.
+Use a shim if you need to support legacy browsers or platforms.
+caniuse promises.
+observable.firstToPromise([PromiseCtr])
returns a Promise which will be resolved with the first event coming from an Observable.
+Like toPromise
, the global ES6 promise implementation will be used unless a promise
+constructor is given.
+observable.toESObservable()
Aliased as observable[Symbol.observable]()
. Returns an
+ES Observable containing the
+events from Bacon observable. This allows Bacon observables to be used with
+Observable.from
and provides interoperability with other ES observable
+implementations such as RxJS and Kefir.
+observable.map(f)
maps values using given function, returning a new
+stream/property. Instead of a function, you can also provide a constant
+value. Further, you can use a property extractor string like
+".keyCode". So, if f is a string starting with a
+dot, the elements will be mapped to the corresponding field/function in the event
+value. For instance map(".keyCode") will pluck the keyCode field from
+the input values. If keyCode was a function, the result stream would
+contain the values returned by the function.
+The Function Construction rules below apply here.
+The map
method, among many others, uses lazy evaluation.
+stream.map(property)
maps the stream events to the current value of
+the given property. This is equivalent to property.sampledBy(stream)
.
+observable.mapError(f)
maps errors using given function. More
+specifically, feeds the "error" field of the error event to the function
+and produces a Next
event based on the return value.
+The Function Construction rules below apply here.
+You can omit the argument to produce a Next
event with undefined
value.
+observable.errors()
returns a stream containing Error
events only.
+Same as filtering with a function that always returns false.
+observable.skipErrors()
skips all errors.
+observable.mapEnd(f)
Adds an extra Next
event just before End. The value is created
+by calling the given function when the source stream ends. Instead of a
+function, a static value can be used. You can omit the argument to
+produce a Next event with undefined
value.
+observable.filter(f)
filters values using given predicate function.
+Instead of a function, you can use a constant value (true
to include all, false
to exclude all) or a
+property extractor string (like ".isValuable") instead. Just like with
+map
, indeed.
+observable.filter(property)
filters values based on the value of a
+property. Event will be included in output if and only if the property holds true
+at the time of the event.
+observable.skipDuplicates(isEqual)
drops consecutive equal elements. So,
+from [1, 2, 2, 1]
you'd get [1, 2, 1]
. Uses the ===
operator for equality
+checking by default. If the isEqual argument is supplied, checks by calling
+isEqual(oldValue, newValue). For instance, to do a deep comparison,you can
+use the isEqual function from underscore.js
+like stream.skipDuplicates(_.isEqual)
.
+observable.take(n)
takes at most n values from the stream and then ends the stream. If the stream has
+fewer than n values then it is unaffected.
+Equal to Bacon.never()
if n <= 0
.
+observable.takeUntil(stream)
takes elements from source until a Next event appears in the other stream.
+If other stream ends without value, it is ignored.
+observable.takeWhile(f)
takes while given predicate function holds true, and then ends.
+Function Construction rules apply.
+observable.takeWhile(property)
takes values while the value of a property holds true, and then ends.
+observable.first()
takes the first element from the stream. Essentially observable.take(1)
.
+observable.last()
takes the last element from the stream. None, if stream is empty.
Note: neverEndingStream.last()
creates the stream which doesn't produce any events and never ends.
+observable.skip(n)
skips the first n elements from the stream
+observable.concat(other)
concatenates two streams/properties into one stream/property so that
+it will deliver events from observable
until it ends and then deliver
+events from other
. This means too that events from other
,
+occurring before the end of observable
will not be included in the result
+stream/property.
+observable.delay(delay)
delays the stream/property by given amount of milliseconds. Does not delay the initial value of a Property
.
+
+
+
+
+observable.throttle(delay)
throttles stream/property by given amount
+of milliseconds. Events are emitted with the minimum interval of
+delay
. The implementation is based on stream.bufferWithTime
.
+Does not affect emitting the initial value of a Property
.
Example:
+
+
+
+
+
+observable.debounce(delay)
throttles stream/property by given amount
+of milliseconds, but so that event is only emitted after the given
+"quiet period". Does not affect emitting the initial value of a Property.
+The difference of throttle
and debounce
is the same as it is in the
+same methods in jQuery.
Example:
+
+
+
+
+observable.debounceImmediate(delay)
passes the first event in the
+stream through, but after that, only passes events after a given number
+of milliseconds have passed since previous output.
Example:
+
+
+
+
+observable.bufferingThrottle(minimumInterval)
throttles the observable using a buffer so that at most one value event in minimumInteval is issued.
+Unlike throttle
, it doesn't discard the excessive events but buffers them instead, outputting
+them with a rate of at most one value per minimumInterval.
Example:
+
+
+
+
+
+observable.doAction(f)
returns a stream/property where the function f
+is executed for each value, before dispatching to subscribers. This is
+useful for debugging, but also for stuff like calling the
+preventDefault()
method for events. In fact, you can
+also use a property-extractor string instead of a function, as in
+".preventDefault"
.
Please note that for Properties, it's not guaranteed that the function will be called exactly once +per event; when a Property loses all of its subscribers it will re-emit its current value when a +new subscriber is added.
+ + +
+observable.doError(f)
returns a stream/property where the function f
+is executed for each error, before dispatching to subscribers.
+That is, same as doAction
but for errors.
+observable.not()
returns a stream/property that inverts boolean values
+observable.flatMap(f)
for each element in the source stream, spawn a new
+stream using the function f
. Collect events from each of the spawned
+streams into the result EventStream
. Note that instead of a function, you can provide a
+stream/property too. Also, the return value of function f
can be either an
+Observable
(stream/property) or a constant value. The result of
+flatMap
is always an EventStream
.
The Function Construction rules below apply here.
+stream.flatMap()
can be used conveniently with Bacon.once()
and Bacon.never()
for converting and filtering at the same time, including only some of the results.
Example - converting strings to integers, skipping empty values:
+
+
+
+
+observable.flatMapLatest(f)
like flatMap
, but instead of including events from
+all spawned streams, only includes them from the latest spawned stream.
+You can think this as switching from stream to stream.
+Note that instead of a function, you can provide a stream/property too.
The Function Construction rules below apply here.
+ + +
+observable.flatMapFirst(f)
like flatMap
, but only spawns a new
+stream if the previously spawned stream has ended.
The Function Construction rules below apply here.
+ + +
+observable.flatMapError(f)
like flatMap
, but is applied only on Error
events. Returned values go into the
+value stream, unless an error event is returned. As an example, one type of error could result in a retry and another just
+passed through, which can be implemented using flatMapError.
+observable.flatMapWithConcurrencyLimit(limit, f)
a super method of flatMap family. It limits the number of open spawned streams and buffers incoming events.
+flatMapConcat
is flatMapWithConcurrencyLimit(1)
(only one input active),
+and flatMap
is flatMapWithConcurrencyLimit ∞
(all inputs are piped to output).
The Function Construction rules below apply here.
+ + +
+observable.flatMapConcat(f)
a flatMapWithConcurrencyLimit
with limit of 1.
The Function Construction rules below apply here.
+ + +
+observable.scan(seed, f)
scans stream/property with given seed value and
+accumulator function, resulting to a Property. For example, you might
+use zero as seed and a "plus" function as the accumulator to create
+an "integral" property. Instead of a function, you can also supply a
+method name such as ".concat", in which case this method is called on
+the accumulator value and the new stream value is used as argument.
Example:
+
+This would result to following elements in the result stream:
+seed value = 0
+0 + 1 = 1
+1 + 2 = 3
+3 + 3 = 6
+
+When applied to a Property as in r = p.scan(seed, f)
, there's a (hopefully insignificant) catch:
+The starting value for r
depends on whether p
has an
+initial value when scan is applied. If there's no initial value, this works
+identically to EventStream.scan: the seed
will be the initial value of
+r
. However, if r
already has a current/initial value x
, the
+seed won't be output as is. Instead, the initial value of r
will be f(seed, x)
. This makes sense,
+because there can only be 1 initial value for a Property at a time.
+observable.fold(seed, f)
is like scan
but only emits the final
+value, i.e. the value just before the observable ends. Returns a
+Property
.
+observable.reduce(seed, f)
synonym for fold
.
+observable.diff(start, f)
returns a Property that represents the result of a comparison
+between the previous and current value of the Observable. For the initial value of the Observable,
+the previous value will be the given start.
Example:
+
+This would result to following elements in the result stream:
+1 - 0 = 1
+2 - 1 = 1
+3 - 2 = 1
+
+
+
+observable.zip(other [, f])
return an EventStream with elements
+pair-wise lined up with events from this and the other EventStream or Property.
+A zipped stream will publish only when it has a value from each
+source and will only produce values up to when any single source ends.
The given function f
is used to create the result value from value in the two
+sources. If no function is given, the values are zipped into an array.
Be careful not to have too much "drift" between streams. If one stream +produces many more values than some other excessive buffering will +occur inside the zipped observable.
+Example 1:
+
+See also zipWith
and zipAsArray
for zipping more than 2 sources.
+observable.slidingWindow(max [, min])
returns a Property that represents a
+"sliding window" into the history of the values of the Observable. The
+result Property will have a value that is an array containing the last n
+values of the original observable, where n
is at most the value of the
+max
argument, and at least the value of the min
argument. If the
+min
argument is omitted, there's no lower limit of values.
For example, if you have a stream s
with value a sequence 1 - 2 - 3 - 4 - 5, the
+respective values in s.slidingWindow(2)
would be [] - [1] - [1,2] -
+[2,3] - [3,4] - [4,5]. The values of s.slidingWindow(2,2)
would be
+[1,2] - [2,3] - [3,4] - [4,5].
+observable.log()
logs each value of the Observable to the console.
+It optionally takes arguments to pass to console.log() alongside each
+value. To assist with chaining, it returns the original Observable. Note
+that as a side-effect, the observable will have a constant listener and
+will not be garbage-collected. So, use this for debugging only and
+remove from production code. For example:
+or just
+
+
+
+
+observable.doLog()
logs each value of the Observable to the console. doLog() behaves like log
+but does not subscribe to the event stream. You can think of doLog() as a
+logger function that – unlike log() – is safe to use in production. doLog() is
+safe, because it does not cause the same surprising side-effects as log()
+does.
+observable.combine(property2, f)
combines the latest values of the two
+streams or properties using a two-arg function. Similarly to scan
, you can use a
+method name instead, so you could do a.combine(b, ".concat")
for two
+properties with array value. The result is a Property.
+observable.withStateMachine(initState, f)
lets you run a state machine
+on an observable. Give it an initial state object and a state
+transformation function that processes each incoming event and
+returns an array containing the next state and an array of output
+events. Here's an example where we calculate the total sum of all
+numbers in the stream and output the value on stream end:
+
+
+
+observable.decode(mapping)
decodes input using the given mapping. Is a
+bit like a switch-case or the decode function in Oracle SQL. For
+example, the following would map the value 1 into the string "mike"
+and the value 2 into the value of the who
property.
+This is actually based on combineTemplate
so you can compose static
+and dynamic data quite freely, as in
+The return value of decode
is always a Property
.
+observable.awaiting(otherObservable)
creates a Property that indicates whether
+observable
is awaiting otherObservable
, i.e. has produced a value after the latest
+value from otherObservable
. This is handy for keeping track whether we are
+currently awaiting an AJAX response:
+
+
+
+observable.endOnError()
ends the Observable
on first Error
event. The
+error is included in the output of the returned Observable
.
+observable.endOnError(f)
ends the Observable
on first Error
event for which
+the given predicate function returns true. The error is included in the
+output of the returned Observable
. The Function Construction rules apply, so
+you can do for example .endOnError(".serious")
.
+observable.withHandler(f)
lets you do more custom event handling: you
+get all events to your function and you can output any number of events
+and end the stream if you choose. For example, to send an error and end
+the stream in case a value is below zero:
+Note that it's important to return the value from this.push
so that
+the connection to the underlying stream will be closed when no more
+events are needed.
+observable.name(newName)
sets the name of the observable. Overrides the default
+implementation of toString
and inspect
.
+Returns itself.
+observable.withDescription(param...)
Sets the structured description of the observable. The toString
and inspect
methods
+use this data recursively to create a string representation for the observable. This method
+is probably useful for Bacon core / library / plugin development only.
For example:
+var src = Bacon.once(1)
+var obs = src.map(function(x) { return -x })
+console.log(obs.toString())
+--> Bacon.once(1).map(function)
+obs.withDescription(src, "times", -1)
+console.log(obs.toString())
+--> Bacon.once(1).times(-1)
+
+
+
+observable.groupBy(keyF [, limitF])
Groups stream events to new streams by keyF
. Optional limitF
can be provided to limit grouped
+stream life. Stream transformed by limitF
is passed on if provided. limitF
gets grouped stream
+and the original event causing the stream to start as parameters.
Calculator for grouped consecutive values until group is cancelled:
+var events = [
+ {id: 1, type: "add", val: 3 },
+ {id: 2, type: "add", val: -1 },
+ {id: 1, type: "add", val: 2 },
+ {id: 2, type: "cancel"},
+ {id: 3, type: "add", val: 2 },
+ {id: 3, type: "cancel"},
+ {id: 1, type: "add", val: 1 },
+ {id: 1, type: "add", val: 2 },
+ {id: 1, type: "cancel"}
+]
+
+function keyF(event) {
+ return event.id
+}
+
+function limitF(groupedStream, groupStartingEvent) {
+ var cancel = groupedStream.filter(function(x) { return x.type === "cancel"}).take(1)
+ var adds = groupedStream.filter(function(x) { return x.type === "add" })
+ return adds.takeUntil(cancel).map(".val")
+}
+
+Bacon.sequentially(2, events)
+ .groupBy(keyF, limitF)
+ .flatMap(function(groupedStream) {
+ return groupedStream.fold(0, function(acc, x) { return acc + x })
+ })
+ .onValue(function(sum) {
+ console.log(sum)
+ // returns [-1, 2, 8] in an order
+ })
+
+
+EventStream
+ + +
+Bacon.EventStream
a stream of events. See methods below.
+stream.merge(otherStream)
merges two streams into one stream that delivers events from both
+stream.holdWhen(valve)
pauses and buffers the event stream if last event in valve is truthy.
+All buffered events are released when valve becomes falsy.
+stream.startWith(value)
adds a starting value to the stream, i.e. concats a
+single-element stream contains value
with this stream.
+stream.skipWhile(f)
skips elements until the given predicate function returns falsy once, and then
+lets all events pass through.
+The Function Construction rules below apply here.
+stream.skipWhile(property)
skips elements until the value of the given Property is falsy once, and then
+lets all events pass through.
+stream.skipUntil(stream2)
skips elements from stream
until a Next event
+appears in stream2
. In other words, starts delivering values
+from stream
after first event appears in stream2
.
+stream.bufferWithTime(delay)
buffers stream events with given delay.
+The buffer is flushed at most once in the given delay. So, if your input
+contains [1,2,3,4,5,6,7], then you might get two events containing [1,2,3,4]
+and [5,6,7] respectively, given that the flush occurs between numbers 4 and 5.
+stream.bufferWithTime(f)
works with a given "defer-function" instead
+of a delay. Here's a simple example, which is equivalent to
+stream.bufferWithTime(10):
+
+
+
+stream.bufferWithCount(count)
buffers stream events with given count.
+The buffer is flushed when it contains the given number of elements. So, if
+you buffer a stream of [1, 2, 3, 4, 5]
with count 2
, you'll get output
+events with values [1, 2]
, [3, 4]
and [5]
.
+stream.bufferWithTimeOrCount(delay, count)
buffers stream events and
+flushes when either the buffer contains the given number elements or the
+given amount of milliseconds has passed since last buffered event.
+stream.toProperty()
creates a Property based on the
+EventStream. Without arguments, you'll get a Property without an initial value.
+The Property will get its first actual value from the stream, and after that it'll
+always have a current value.
+stream.toProperty(initialValue)
creates a Property based on the
+EventStream with the given initial value that will be used as the current value until
+the first value comes from the stream.
+stream.flatScan(seed, f)
scans stream with given seed value and accumulator function, resulting to a Property.
+Difference to scan
is that the function f
can return an EventStream
or a Property
instead
+of a pure value, meaning that you can use flatScan
for asynchronous updates of state. It serializes
+updates so that that the next update will be queued until the previous one has completed.
Property
+ + +
+Bacon.Property
a reactive property. Has the concept of "current value".
+You can create a Property from an EventStream by using either toProperty
+or scan
method. Note: depending on how a Property is created, it may or may not
+have an initial value. The current value stays as its last value after the stream has ended.
+Bacon.constant(x)
creates a constant property with value x.
+property.assign(obj, method [, param...])
calls the method of the given
+object with each value of this Property. You can optionally supply
+arguments which will be used as the first arguments of the method call.
+For instance, if you want to assign your Property to the "disabled"
+attribute of a JQuery object, you can do this:
+A simpler example would be to toggle the visibility of an element based +on a Property:
+
+Note that the assign
method is actually just a synonym for onValue
and
+the function construction rules below apply to both.
+property.sample(interval)
creates an EventStream by sampling the
+property value at given interval (in milliseconds)
+property.sampledBy(stream)
creates an EventStream by sampling the
+property value at each event from the given stream. The result
+EventStream will contain the property value at each event in the source
+stream.
+property.sampledBy(property)
creates a Property by sampling the
+property value at each event from the given property. The result
+Property will contain the property value at each event in the source
+property.
+property.sampledBy(streamOrProperty, f)
samples the property on stream
+events. The result values will be formed using the given function
+f(propertyValue, samplerValue)
. You can use a method name (such as
+".concat") instead of a function too.
+property.changes()
returns an EventStream
of property value changes.
+Returns exactly the same events as the property itself, except any Initial
+events. Note that property.changes()
does NOT skip duplicate values, use .skipDuplicates() for that.
+property.and(other)
combines properties with the &&
operator.
+property.or(other)
combines properties with the ||
operator.
+property.startWith(value)
adds an initial "default" value for the
+Property. If the Property doesn't have an initial value of it's own, the
+given value will be used as the initial value. If the property has an
+initial value of its own, the given value will be ignored.
Combining multiple streams and properties
+ + +
+Bacon.combineAsArray(streams)
combines Properties, EventStreams and
+constant values so that the result Property will have an array of all
+property values as its value. The input array may contain both Properties
+and EventStreams. In the latter case, the stream is first converted into
+a Property and then combined with the other properties.
+Bacon.combineAsArray(s1, s2...)
just like above, but with streams
+provided as a list of arguments as opposed to a single array.
+
+
+
+Bacon.combineWith(f, stream1, stream2...)
combines given n Properties,
+EventStreams and constant values using the given n-ary function f(v1, v2 ...)
.
+To calculate the current sum of three numeric Properties, you can do
+
+
+
+Bacon.combineWith(f, streams)
like above, but with streams provided as a single array as opposed to a list
+of arguments.
+
+
+
+Bacon.combineWith(streams, f)
like above
+Bacon.combineWith(stream1, stream2..., f)
like above
+Bacon.combineTemplate(template)
combines Properties, EventStreams and
+constant values using a template
+object. For instance, assuming you've got streams or properties named
+password
, username
, firstname
and lastname
, you can do
+.. and your new loginInfo property will combine values from all these +streams using that template, whenever any of the streams/properties +get a new value. For instance, it could yield a value such as
+
+In addition to combining data from streams, you can include constant +values in your templates.
+Note that all Bacon.combine* methods produce a Property instead of an EventStream.
+If you need the result as an EventStream
you might want to use property.changes()
+
+
+
+Bacon.mergeAll(streams)
merges given array of EventStreams or Properties. Returns an EventStream. See merge
Bacon.mergeAll(stream1, stream2 ...)
merges given EventStreams.
+Bacon.concatAll(streams)
concatenates given array of EventStreams or Properties, returns an EventStream. See concat
Bacon.concatAll(stream1, stream2 ...)
concatenates given EventStreams.
+Bacon.zipAsArray(streams)
zips the array of EventStreams / Properties in to a new
+EventStream that will have an array of values from each source as
+its value. Zipping means that events from each source are combined
+pairwise so that the 1st event from each source is published first, then
+the 2nd event from each. The results will be published as soon as there
+is a value from each source.
Be careful not to have too much "drift" between streams. If one stream +produces many more values than some other excessive buffering will +occur inside the zipped observable.
+Example:
+
+
+
+
+Bacon.zipAsArray(stream1, stream2...)
just like above, but with sources
+provided as a list of arguments as opposed to a single array.
+Bacon.zipWith(streams, f)
like zipAsArray
but uses the given n-ary
+function to combine the n values from n sources, instead of returning them in an Array.
+Bacon.zipWith(f, streams)
like zipAsArray
but uses the given n-ary
+function to combine the n values from n sources, instead of returning them in an Array.
+Bacon.zipWith(f, stream1, stream1...)
like above
+Bacon.zipWith(stream1, stream1..., f)
like above
+Bacon.onValues(a, b [, c...], f)
is a shorthand for combining multiple
+sources (streams, properties, constants) as array and assigning the
+side-effect function f for the values. The following example would log
+the number 3.
+
+
+Function Construction rules
+ + +Many methods in Bacon have a single function as their argument. Many of these +actually accept a wider range of different arguments that they use for +constructing the function.
+Here are the different forms you can use, with examples. The basic form +would be
+stream.map(f)
maps values using the function f(x)
As an extension to the basic form, you can use partial application:
+stream.map(f, "bacon")
maps values using the function f(x, y), using
+"bacon" as the first argument, and stream value as the second argument.
stream.map(f, "pow", "smack")
maps values using the function f(x, y,
+z), using "pow" and "smack" as the first two arguments and stream value
+as the third argument.
Then, you can create method calls like this:
+stream.onValue(object, method)
calls the method having the given name,
+with stream value as the argument.
titleText.onValue($("#title"), "text")
which would call the "text" method of the jQuery object matching to the HTML element with the id "title"
disableButton.onValue($("#send"), "attr", "disabled")
which would call
+the attr method of the #send element, with "disabled" as the first
+argument. So if your property has the value true
, it would call
+$("#send").attr("disabled", true)
You can call methods or return field values using a "property extractor" +syntax. With this syntax, Bacon checks the type of the field and if it's indeed a method, it calls it. Otherwise it just returns field value. For example:
+stream.map(".length")
would return the value of the "length" field of
+stream values. Would make sense for a stream of arrays. So, you'd get 2
+for ["cat", "dog"]
stream.map(".stuffs.length")
would pick the length of the "stuffs"
+array that is a field in the stream value. For example, you'd get 2 for
+{ stuffs : ["thing", "object"] }
stream.map(".dudes.1")
would pick the second object from the nested
+"dudes" array. For example, you'd get "jack" for { dudes : ["john", "jack"] }
.
stream.doAction(".preventDefault")
would call the "preventDefault" method of
+stream values.
stream.filter(".attr", "disabled").not()
would call .attr("disabled")
on
+stream values and filter by the return value. This would practically
+inlude only disabled jQuery elements to the result stream.
If none of the above applies, Bacon will return a constant value. For +instance:
+mouseClicks.map({ isMouseClick: true })
would map all events to the
+object { isMouseClick: true }
Methods that support function construction include
+at least onValue
, onError
, onEnd
, map
, filter
, assign
, takeWhile
, mapError
and doAction
.
Lazy evaluation
+ + +Methods such as map
and the combine
use lazy evaluation to avoid evaluating
+values that aren't actually needed. This can be generally considered a Good Thing,
+but it has it's pitfalls.
If you pass a function that referentially transparent, you'll +be fine. This means that your function should return the same value regardless of +when it's called.
+On the other hand, if you pass a function that returns a value depending on time,
+you may have problems. Consider a property contents
that's derived from events
+like below.
+Now the submittedItems
stream will produce the current value of the items
property
+when an event occurs in the submitClick
stream. Or so you'd think. In fact, the value
+of submittedItems
is evaluated at the time of the event in the submitClick
stream,
+which means that it will actually produce the value of getCurrentValueFromUI
at that time,
+instead of at the time of the original click
event.
To force evaluation at the time of original event, you can just use flatMap
instead of map
.
+As in here.
+
+
+
+Latest value of Property or EventStream
+ + +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
+ + +Bus
is an EventStream
that allows you to push
values into the stream.
+It also allows plugging other streams into the Bus. The Bus practically
+merges all plugged-in streams and the values pushed using the push
+method.
+new Bacon.Bus()
returns a new Bus.
+bus.push(x)
pushes the given value to the stream.
+bus.end()
ends the stream. Sends an End event to all subscribers.
+After this call, there'll be no more events to the subscribers.
+Also, the bus.push
and bus.plug
methods have no effect.
+bus.error(e)
sends an Error with given message to all subscribers
+bus.plug(stream)
plugs the given stream to the Bus. All events from
+the given stream will be delivered to the subscribers of the Bus.
+Returns a function that can be used to unplug the same stream.
The plug method practically allows you to merge in other streams after +the creation of the Bus. I've found Bus quite useful as an event broadcast +mechanism in the +Worzone game, for instance.
+ + +Event
+ + +
+Bacon.Event
has subclasses Bacon.Next
, Bacon.End
, Bacon.Error
and Bacon.Initial
+Bacon.Next
next value in an EventStream or a Property. Call isNext() to
+distinguish a Next event from other events.
+Bacon.End
an end-of-stream event of EventStream or Property. Call isEnd() to
+distinguish an End from other events.
+Bacon.Error
an error event. Call isError() to distinguish these events
+in your subscriber, or use onError
to react to error events only.
+errorEvent.error
returns the associated error object (usually string).
+Bacon.Initial
the initial (current) value of a Property. Call isInitial() to
+distinguish from other events. Only sent immediately after subscription
+to a Property.
Event properties and methods
+ + +
+event.value()
returns the value associated with a Next or Initial event
+event.hasValue()
returns true for events of type Initial and Next
+event.isNext()
true for Next events
+event.isInitial()
true for Initial events
+event.isError()
true for Error events
+event.isEnd()
true for End events
+event.error
the error value of Error events
Errors
+ + +Bacon.Error
events are always passed through all stream combinators. 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 the observable.onError(f)
+callback.
See documentation on onError
, mapError
, errors
, skipErrors
, Bacon.retry
and flatMapError
above.
In case you want to convert (some) value events into Error
events, you may use flatMap
like this:
+Conversely, if you want to convert some Error
events into value events, you may use flatMapError
:
+Note also that Bacon.js combinators 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:
+For example, you can use Bacon.try
to handle JSON parse errors:
+An Error does not terminate the stream. The method observable.endOnError()
+returns a stream/property that ends immediately after first error.
Bacon.js doesn't currently generate any Error
events itself (except when
+converting errors using Bacon.fromPromise). Error
+events definitely would be generated by streams derived from IO sources
+such as AJAX calls.
+Bacon.retry(options)
is used to retry the call when there is an Error
event in the stream produced by the source
function.
The two required option parameters are:
+-
+
source
, a function that produces an Observable. The function gets attempt number (starting from zero) as its argument.
+retries
, the number of times to retry thesource
function in addition to the initial attempt. Use the value o (zero) for retrying indefinitely.
+
Additionally, one may pass in one or both of the following callbacks:
+-
+
isRetryable
, a function returningtrue
to continue retrying,false
to stop. Defaults totrue
. The error that occurred is given as a parameter. For example, there is usually no reason to retry a 404 HTTP error, whereas a 500 or a timeout might work on the next attempt.
+delay
, a function that returns the time in milliseconds to wait before retrying. Defaults to0
. The function is given a context object with the keyserror
(the error that occurred) andretriesDone
(the number of retries already performed) to help determine the appropriate delay e.g. for an incremental backoff.
+
+
+
+Join Patterns
+ + +Join patterns are a generalization of the zip
function. While zip
+synchronizes events from multiple streams pairwse, join patterns allow
+for implementation of more advanced synchronization patterns. Bacon.js
+uses the Bacon.when
function to convert a list of synchronization
+patterns into a resulting eventstream.
+Bacon.when
Consider implementing a game with discrete time ticks. We want to
+handle key-events synchronized on tick-events, with at most one key
+event handled per tick. If there are no key events, we want to just
+process a tick.
+Order is important here. If the [tick] patterns had been written +first, this would have been tried first, and preferred at each tick.
+Join patterns are indeed a generalization of zip, and for EventStreams, zip is +equivalent to a single-rule join pattern. The following observables +have the same output, assuming that all sources are EventStreams.
+
+Note that Bacon.when
does not trigger updates for events from Properties though;
+if you use a Property in your pattern, its value will be just sampled when all the
+other sources (EventStreams) have a value. This is useful when you need a value of a Property
+in your calculations. If you want your pattern to fire for a Property too, you can
+convert it into an EventStream using property.changes()
or property.toEventStream()
+Bacon.update
creates a Property from an initial value and updates the value based on multiple inputs.
+The inputs are defined similarly to Bacon.when
, like this:
+As input, each function above will get the previous value of the result
Property, along with values from the listed Observables.
+The value returned by the function will be used as the next value of result
.
Just like in Bacon.when
, only EventStreams will trigger an update, while Properties will be just sampled.
+So, if you list a single EventStream and several Properties, the value will be updated only when an event occurs in the EventStream.
Here's a simple gaming example:
+
+In the example, the score
property is updated when either hitUfo
or hitMotherShip
occur. The scoreMultiplier
Property is sampled to take multiplier into account when hitUfo
occurs.
Join patterns as a "chemical machine"
+ + +A quick way to get some intuition for join patterns is to understand
+them through an analogy in terms of atoms and molecules. A join
+pattern can here be regarded as a recipe for a chemical reaction. Lets
+say we have observables oxygen
, carbon
and hydrogen
, where an
+event in these spawns an 'atom' of that type into a mixture.
We can state reactions
+
+Now, every time a new 'atom' is spawned from one of the observables,
+this atom is added to the mixture. If at any time there are two hydrogen
+atoms, and an oxygen atom, the corresponding atoms are consumed,
+and output is produced via make_water
.
The same semantics apply for the second rule to create carbon +monoxide. The rules are tried at each point from top to bottom.
+ + + +Join patterns and properties
+ + +Properties are not part of the synchronization pattern, but are
+instead just sampled. The following example take three input streams
+$price
, $quantity
and $total
, e.g. coming from input fields, and
+defines mutally recursive behaviours in properties price
, quantity
+and total
such that
-
+
- updating price sets total to price * quantity +
- updating quantity sets total to price * quantity +
- updating total sets price to total / quantity +
+
+
+
+Join patterns and Bacon.bus
+ + +The result functions of join patterns are allowed to push values onto
+a Bus
that may in turn be in one of its patterns. For instance, an
+implementation of the dining philosophers problem can be written as
+follows. (http://en.wikipedia.org/wiki/Dining_philosophers_problem)
Example:
+
+
+
+
+Introspection and metadata
+ + +Bacon.js provides ways to get some descriptive metadata about all Observables.
+ + + +
+observable.toString
Returns a textual description of the Observable. For instance,
+Bacon.once(1).map(function() {}))
would return "Bacon.once(1).map(function)".
+observable.deps
Returns the an array of dependencies that the Observable has. For instance, for a.map(function() {}).deps()
, would return [a]
.
+This method returns the "visible" dependencies only, skipping internal details. This method is thus suitable for visualization tools.
+Internally, many combinator functions depend on other combinators to create intermediate Observables that the result will actually depend on.
+The deps
method will skip these internal dependencies.
+observable.internalDeps
Returns the true dependencies of the observable, including the intermediate "hidden" Observables.
+This method is for Bacon.js internal purposes but could be useful for debugging/analysis tools as well.
+observable.desc()
Contains a structured version of what toString
returns.
+The structured description is an object that contains the fields context
, method
and args
.
+For example, for Bacon.fromArray([1,2,3]).desc
you'd get
{ context: Bacon, method: "fromArray", args: [[1,2,3]] }
+
+Notice that this is a field, not a function.
+ + +
+Bacon.spy(f)
+Adds your function as a "spy" that will get notified on all new Observables.
+This will allow a visualization/analytis tool to spy on all Bacon activity.
Cleaning up
+ + +As described above, a subscriber can signal the loss of interest in new events +in any of these two ways:
+-
+
- Return
Bacon.noMore
from the handler function
+ - Call the
dispose()
function that was returned by thesubscribe()
+call.
+
Based on my experience on RxJs coding, an actual side-effect subscriber +in application-code never does this. So the business of unsubscribing is +mostly internal business and you can ignore it unless you're working on +a custom stream implementation or a stream combinator. In that case, I +welcome you to contribute your stuff to bacon.js.
+ + + +EventStream and Property semantics
+ + +The state of an EventStream can be defined as (t, os) where t
is time
+and os
the list of current subscribers. This state should define the
+behavior of the stream in the sense that
-
+
- When a Next event is emitted, the same event is emitted to all subscribers +
- After an event has been emitted, it will never be emitted again, even +if a new subscriber is registered. A new event with the same value may +of course be emitted later. +
- When a new subscriber is registered, it will get exactly the same +events as the other subscriber, after registration. This means that the +stream cannot emit any "initial" events to the new subscriber, unless it +emits them to all of its subscribers. +
- A stream must never emit any other events after End (not even another End) +
The rules are deliberately redundant, explaining the constraints from +different perspectives. The contract between an EventStream and its +subscriber is as follows:
+-
+
- For each new value, the subscriber function is called. The new
+value is wrapped into a
Next
event.
+ - The subscriber function returns a result which is either
Bacon.noMore
or +Bacon.more
. Theundefined
value is handled likeBacon.more
.
+ - In case of
Bacon.noMore
the source must never call the subscriber again.
+ - When the stream ends, the subscriber function will be called with
+and
Bacon.End
event. The return value of the subscribe function is +ignored in this case.
+
A Property
behaves similarly to an EventStream
except that
-
+
- On a call to
subscribe
, it will deliver its current value +(if any) to the provided subscriber function wrapped into anInitial
+event.
+ - This means that if the Property has previously emitted the value
x
+to its subscribers and that is the latest value emitted, it will deliver +this value to the new subscriber.
+ - Property may or may not have a current value to start with. Depends +on how the Property was created. +
Atomic updates
+ + +From version 0.4.0, Bacon.js supports atomic updates to properties, with +known limitations.
+Assume you have properties A and B and property C = A + B. Assume that +both A and B depend on D, so that when D changes, both A and B will +change too.
+When D changes d1 -> d2
, the value of A a1 -> a2
and B changes b1 -> b2
simultaneously, you'd like C to update atomically so that it
+would go directly a1+b1 -> a2+b2
. And, in fact, it does exactly that.
+Prior to version 0.4.0, C would have an additional transitional
+state like a1+b1 -> a2+b1 -> a2+b2
Atomic updates are limited to Properties only, meaning that simultaneous +events in EventStreams will not be recognized as simultaneous and may +cause extra transitional states to Properties. But as long as you're +just combining Properties, you'll updates will be atomic.
+ + + +For RxJs Users
+ + +Bacon.js is quite similar to RxJs, so it should be pretty easy to pick up. The +major difference is that in bacon, there are two distinct kinds of Observables: +the EventStream and the Property. The former is for discrete events while the +latter is for observable properties that have the concept of "current value".
+Also, there are no "cold observables", which +means also that all EventStreams and Properties are consistent among subscribers: +when as event occurs, all subscribers will observe the same event. If you're +experienced with RxJs, you've probably bumped into some wtf's related to cold +observables and inconsistent output from streams constructed using scan and startWith. +None of that will happen with bacon.js.
+Error handling is also a bit different: the Error event does not
+terminate a stream. So, a stream may contain multiple errors. To me,
+this makes more sense than always terminating the stream on error; this
+way the application developer has more direct control over error
+handling. You can always use stream.endOnError()
to get a stream
+that ends on error!
An
+EventStream
that allows you topush
values into the stream.It also allows plugging other streams into the Bus, as inputs. The Bus practically + merges all plugged-in streams and the values pushed using the
+push
+ method.