Emit
Simple Reactive JavaScript library using ES6 generators/iterators
Install / Use
/learn @DanShappir/EmitREADME
Emit
Emit is a light-weight, Open Source library for Reactive Programming using JavaScript. Emit utilizes ECMAScript 6 (ES6) generators and iterators for implementing observable sequences. As a result, Emit is very concise, and easily extensible. Despite its compact size, Emit provides a wide variety of operators for observable sequences, modeled after array iteration methods. This makes Emit easy to use, in a way that will be familiar to most JavaScript developers.
// Clock that updates every second, using Emit + jQuery
Emit.interval(1000).map(() => (new Date).toLocaleTimeString()).forEach((t) => $clock.text(t));
Emit works well with most any JavaScript library, for example jQuery. In addition, Emit works with and leverages Promises, as well as any other thenable object. Because Emit is small and simple to use, it provides an easy way to start leveraging the benefits of Reactive programming in your JavaScript applications.
Emit is compatible with the released versions of Chrome, Firefox and Opera. Note that the code snippets in this document utilize the arrow function notation, which is currently only supported by Firefox.
Installation
Simply use Bower:
- Install Bower: npm install -g bower
- Install the package: bower install Emit
- Reference the file: bower_components/Emit/js/emit.js
Examples
In addition to the examples included in this repository, there are several online examples:
- Wikipedia autocomplete at JSFiddle
- Draw on canvas at JSFiddle
- Color picker at JSFiddle
- Sum numeirc fields at JSFiddle
- Time flies like an arrow at JSFiddle
Terminology
- Observable sequence - a sequence of events over time that behaves like a a sequence of elements in [memory] space, such as arrays. The Emit library creates and manipulates observable sequences.
- Source / emitter - the origin (left-hand side)of an observable sequence. The source places data items into the observable sequence. The source can also throw exceptions (signal errors) on the observable sequence.
- Consumer - the target (right-hand side) of an observable sequence. The target is automatically invoked for every data item placed into the observable sequence.
Design and implementation notes
- Emit is a library, not a framework. It does not mandate any usage methodology, and plays nice with other libraries and frameworks, e.g. jQuery
- Emit is small and compact, and implements a simple yet complete API
- Emit observable sequences are always hot. This means that they do not need to be explicitly enabled.
- There is no subscribe / unsubscribe mechanism - simply attach a consumer to an observable sequence to start receiving data. Indicate that the consumer no longer requires data items using methods such as until and head.
- There is no notification for end-of-data on an observable sequence. Consumers only receive notifications for data and for errors (exceptions).
- Sources for observable sequence are notified when consumers no longer require data, for example as result of using head. They can use this notification to release resources or detach from events.
API
Emit functions fall into two main categories:
- Functions in the Emit namespace, such as Emit.events
- Methods on observable sequences, such as filter
The first category mainly provides functions for creating new observable sequences from data sources, or for combining multiple existing sequences into a single sequence. The second category provides methods for manipulating the sequence or processing its members.
Functions in Emit namespace
Emit.isEmitter(candidate)
Returns true if candidate is an observable sequence. Returns false otherwise.
Emit.create(source[, done])
Create a new observable sequence from a data source, and returns that sequence. This API takes two functions as arguments:
- source - called whenever an active consumer is attached to observable sequence, with a single argument which implements the iterator interface. Invoke the next method with a value to push that value into the sequence. Invoke the throw method with an error object to signal an error on the sequence.
- done (optional) - called whenever a consumer is no longer active, for example as result of until or head. The same iterator argument is also passed to this function.
In addition, both functions are invoked with the same context, which is initially an empty object. The functions can utilize this context to store private state information.
// notify every second. Each consumer will utilize a distinct setInterval instance
var interval = Emit.create(function (iter) {
this.id = window.setInterval(iter.next, 1000);
}, function () {
window.clearInterval(this.id);
});
Emit.reusable(source[, done])
Like Emit.create with enhanced support for multiple concurrent consumers. source is invoked only when the number of active consumers goes up from 0 to 1. And done is called only when the number of active consumers goes back down to 0. Any value emitted will be transferred to all the active consumers.
// notify every second. All concurrent consumers utilize a single setInterval instance
var id;
var interval = Emit.reusable(function (iter) {
id = window.setInterval(iter.next, 1000);
}, function () {
window.clearInterval(id);
});
Emit.value(v)
Create a new observable sequence which contains a single value.
Emit.value(42).forEach((v) => console.log(v)); // output 42
If the provided value is thenable (has a then method) then its success value will be pushed into the observable sequence, and if it is rejected then it will be signaled as an error on the sequence.
Emit.value(Promise.resolve('tada')).forEach((v) => console.log(v)); // output tada
Emit.sequence(s[, depth])
Create a new observable sequence from a sequence of items, such as an array or another observable sequence. As sequence in this context is any object which implements forEach. Emit.sequence spreads the items of the input sequence in the resulting observable sequence by internally passing the depth argument to flatten. If depth is not specified, a default value 0 (non recursive) is used.
Emit.sequence([1, 2, 3]).forEach((v) => console.log(v)); // output 1, 2, 3
Emit.iter()
Create a new observable sequence that also implement the ES6 iterator interface: next and throw. To emit values into the observable sequence, invoke next with the values as arguments. To signal an error on the sequence, call throw and provide the error object.
The next method returns an object that has a done property that is true is the observable sequence is no longer accepting elements, and false otherwise.
Note: unlike standard ES6 iterators, an observable sequence created by Emit.iter can revert back from done to not done when a new consumer is attached to it.
var seq = Emit.inject().forEach((v) => console.log(v));
seq.next(42); // Outputs 42
Emit.matcher(test[, until])
Create a new observable sequence that also implement the ES6 iterator interface: next and throw, and also implements a test method. Such an observable sequence is intended to be used as an argument for the match method. The test argument can be either a function, in which case it will be attached as-is to the resulting observable sequence, or itself be an observable sequence. If the latter, then the latest getter will be used to extract the last value from that sequence, and its truthiness will be used to determine the match.
If test is an observable sequence then a second argument until can be specified to control its duration.
var matcher = Emit.matcher((v) => v % 2);
var source = Emit.iter();
Emit.match(matcher);
matcher.forEach((v) => console.log(v, 'is odd'));
source.next(7); // output "7 is odd"
source.next(8); // no output
Emit.merge([s1, s2, ...] | s1, s2, ...)
Given a single argument containing an array of one or more observable sequences, or one or more observable sequences provided as distinct arguments, creates a new observable sequence which contains the elements of all these sequences. No order is guaranteed between the emitted elements, instead new elements are added to the output observable sequence as soon as they arrive on the input sequence.
If any one of the input sequences throws an exception, that exception will be rethrown into the output observable sequence.
// mouseButton will emit true whenever a mouse button is pressed, and false whenever a mouse button is released
var mouseButton = Emit.merge(Emit.events('mousedown', canvas).map(true), Emit.events('mouseup', canvas).map(false));
Emi.sync([s1, s2, ...] | s1, s2, ...)
Given a single argument containing an array of one or more observable sequences, or one or more observable sequences provided as distinct arguments, creates a new observable sequence which contains arrays as elements. Each such array contains elements from the input sequences, in order. The output is synchronized with all the input. This means a new elements will be emitted only after all the input sequences have provided new elements. As a result, if one input sequence emits two or more elements before the other sequences emit, only its last element will be used, and the others will be discarded.
If any one of the input sequences throws an exception, that exception will be rethrown into the output observable sequence.
// se
