This is a library providing functional reactive programming (FRP) primitives and operations.
npm install frp
Where appropriate, functions are provided both as module-level functions and as methods on the FRP objects themselves.
Wherever it makes sense, functions accepting callbacks will also accept lodash-style callback specifiers instead of functions.
require('frp/event')
require('frp').event
An event is something that can be watched for a value:
event.watch(cb)
event.bind(cb)
event.unwatch(cb)
event.unbind(cb)
The two forms are equivalent.
The given callback may be called any number of times in the future, with one argument (the “value”) each time. This is referred to as the event “firing” that value.
The watch
and bind
functions return a function which can be used instead of
the unwatch
or unbind
functions.
This returns a Pipe, which is an event that has an extra function fire(value)
which causes it to fire a value. It also has a property event
which refers to
a copy of itself without the fire function.
A useful pattern is to create a pipe, keep a reference to it, and return pipe.event to your caller so that other code can listen to the event, but not cause it to fire.
Return an event that listens for the specified DOM event and fires the DOM
Event
object.
Returns a new event which, whenever any of the given events fires a value, fires that value.
This is an event which never fires and as such is the identity with respect to
Event.combine
.
Returns a new event which, when the original event fires a value, fires
fn(value)
.
Returns a new event which keeps an internal value, initialized to the given
initial value. When the given event fires a value x
, the returned event will
update its internal value to fn(oldInternal, x)
and fire that value.
Returns a new event which behaves identically to the given event, but which also
has a release
function. When called, this breaks the link between the original
event and the returned event.
There are times when an Event will not return a value but rather an error message should be sent.
For this reason frp.error
was created. frp.error
will not return a standard
Javascript Error
but simple an Object
with an error message. This allows for an frp.event
to fire regular Javascript errors also.
In the same way you can watch
and bind
an event you can watch for error messages using
the catch
function or method:
Example:
var pipe = frp.event.pipe();
pipe
.watch(function(value) {
console.log(value); // will log 'Will go into watch'
})
.catch(function(error) {
console.log(error.message); // will log ''
});
pipe.fire('Will go into watch');
pipe.fire(frp.error('Will go into catch'));
In the same way you can unwatch
a watch
you can uncatch
a catch
.
require('frp/signal')
require('frp').signal
A signal represents a changing value. It is like an event except that it has a current value:
signal.value
signal.get()
Signals can also be empty, in which case signal.empty
will be true and
signal.value
and signal.get()
will be undefined.
Signals can be watched, just like events:
signal.watch(cb)
signal.unwatch(cb)
There is also a similar function, bind
:
signal.bind(cb)
signal.unbind(cb)
In this case, cb(value)
will be called whenever the signal’s value changes
just like with watch().
Additionally, though, cb(value)
will be called once
as soon as bind()
is called, with the signal’s current value.
Returns a signal whose value is always the same.
Returns an empty signal whose value is updated with any value fired by the given event.
Returns a signal with the given initial value, but whose value is updated with any value fired by the given event.
Returns a Cell
object, which is to a signal what a pipe is to an event; that
is, an object like a signal but which can be written to.
Write to a cell using either its set()
method or its value
property.
Use cell.signal
to get an ordinary read-only signal from the cell.
Accepts either an array of signals or an object mapping names to signals.
If given an object, returns a signal whose value is an object mapping names to the values of the named signals in the argument object. If any of the named signals are empty, their names will not be present in the returned signal’s value.
If given an array, returns a signal whose value is an array whose elements are the values of the corresponding signals in the given array. If any of the given signals are empty, the corresponding items in the value array will be left unset.
Accepts either an array of signals or an object mapping names to signals.
Behaves as Signal.combine
, except join
waits for all given signals to have
values before starting to emit values.
Signals support all the functions that events do, and also offer the following functions:
If the given signal has a value, returns the given signal. Otherwise, returns a signal which has the given value, and which starts to track the given signal’s value as soon as it has one.
The argument should be a signal whose value is always another signal.
Returns a new signal whose value is always the value of the signal that is the value of the given signal.
If the given signal ever takes on a value which is not a signal, undefined behaviour ensues.
Equivalent to signal.map(fn).flatten()
When using transformative functions like map
or filter
to create new events
and signals out of old ones, the old (“parent”) event or signal will retain
a reference to the new (“child”) signal for as long as it lives, unless it is
specifically unbound.
This means that child objects will live at least as long as their parents!
So, if you have a long-lived object and you keep calling map
on it, its
internal list of watchers will keep growing ever longer, even after you’ve
stopped using the new objects that map
has created.
This may not be what you want, which is where the ref
function comes in:
Calling ref
on a signal or event creates an object that behaves identically
to the object you called ref
on, except that the new object will have a
release
method. Calling this will cause it to become disconnected from its
parent, no longer mirroring its updates.
This allows you to manage the lifetime of connected chains of events and signals. For example:
var a = getSuperLongLivedEvent();
var a0 = a.ref();
a0.map(blah).filter(bling).watch(function(x) {
...
});
// done with a0
a0.release();
var a1 = a.ref();
a1.map(bloo).filter(blarp).watch(function(y) {
...
});
// done with a1
a1.release();
In this example, the intermediary events created by the calls to map
and
filter
would normally stick around forever, bloating a
’s list of watchers.
But, since a0
and a1
are created using a.ref
, calling release
causes
them to disconnect from a
and become unreachable. a
’s list of watchers will
be empty again after both a0
and a1
are released, and a0
and a1
can
be garbage-collected.