Skip to content

Instantly share code, notes, and snippets.

@webstrand
Created August 5, 2016 14:56
Show Gist options
  • Select an option

  • Save webstrand/d66722bc4f604b79a75de3cac4c6a548 to your computer and use it in GitHub Desktop.

Select an option

Save webstrand/d66722bc4f604b79a75de3cac4c6a548 to your computer and use it in GitHub Desktop.
Prototype pub-sub library
// Module streams
//
// This is effectively a pub-sub system with a few extra convenience methods.
// Fundamentally, observers are attached to a stream or property, and are called
// when the stream or property emits an event.
//
// By and large, most of the effort in this module is devoted to creating
// optimized streams and properties for passing around an arbitrary amount
// argument variables rather than wrapping up multi-part events into arrays.
//
// In some cases, this method is faster because the Javascript VM doesn't have
// to pack and unpack arrays as each observer reads the event. And none of the
// observers can accidentally mutate the event object itself.
//
// To generate the various argument-width streams and properties, functions are
// dynamically generated at runtime. There is a slight performance hit due to
// this compilation, but for long-running apps the effect is negligible.
//
//
// There are two fundamental types that are generated by this module:
//
// First are streams, which are simply publisher objects. The various argument-
// widths from 0 to 6 (currently) can be accessed through the Stream0 .. Stream6
// constructors.
//
// Second are properties, which are like streams, except that they persist the
// last event they emitted and immediately re-emit that saved event to any new
// observer that is bound. The various argument-widths from 0 to 6 (currently)
// can be accessed through the Property0 .. Property6 constructors.
(function (exports) {
"use strict";
// Argument names used in dynamically generated functions.
var arg_names = [ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z' ];
// Many of the higher-order functions require an index mapping integers to
// Stream and Property widths.
var subordinate_stream_index = [];
var subordinate_property_index = [];
// Prototype shared by all Stream objects regardless of width.
var shared_stream_prototype = {
stream__stream_index: subordinate_stream_index,
stream__property_index: subordinate_property_index,
/**
* stream.on_event(fn, context)
*
* Attach a new observer to the stream, and return a handle for later
* removal. Observers will be called for each new event in the order
* that they were attached.
*
* @param {Function} fn Event callback function. Should consume as many
* arguments as the Stream's width.
*
* @param {Object} context Context in which to call fn.
*
* @returns {Handle} Handle of callback binding.
*/
on_event: function (fn, context) {
this.stream__observers.push(fn, context);
return [fn, context];
},
/**
* stream.off_event(handle)
*
* Remove an observer from the stream.
*
* @param {Handle} handle Handle returned by on_event.
*/
/**
* stream.off_event(fn, context)
*
* Remove an observer from the stream. Only one instance of fn and
* context are removed from the stream.
*
* @param {Function} fn Event callback function.
* @param {Object} context Context in which to call fn.
*/
off_event: function (fn_or_handle, ctxt) {
// Decide if we're being called with a handle or a function-context
// pair.
var fn, context;
if(fn_or_handle instanceof Array) {
fn = fn_or_handle[0];
context = fn_or_handle[1];
}
else {
fn = fn_or_handle;
context = ctxt;
}
// Iterate through the observers until something matches our
// function-context pair, then splice it out.
var observers = this.stream__observers;
for(var i = 0, length = observers.length; i !== length; i += 2) {
if(observers[i] === fn && observers[i + 1] === context) {
observers.splice(i, 2);
break;
}
}
},
/**
* stream.off_event_all()
*
* Remove all observers from the stream.
*/
off_event_all: function () {
this.stream__observers = [];
},
/**
* stream.to_stream()
*
* Convert the property or stream into a stream. When called on a
* property, a new object may be created. When called on streams the
* stream is returned.
*
* @returns {this}
*/
to_stream: function () {
return this;
},
/**
* stream.destroy()
*
* Destroy the stream, removing all observers and any associated
* metadata.
*/
destroy: null
};
// For now, destroy is just an alias.
shared_stream_prototype.destroy = shared_stream_prototype.off_event_all;
var shared_subordinate_stream_prototype = {
// See stream.on_event
on_event: function (fn, context) {
// If the stream isn't active, activate the stream.
if(this.stream__active === false) {
this.stream__active = true;
this.stream__open();
}
this.stream__observers.push(fn, context);
return [fn, context];
},
// See stream.off_event
off_event: function(fn_or_handle, ctxt) {
// Decide if we're being called with a handle or a function-context
// pair.
var fn, context;
if(fn_or_handle instanceof Array) {
fn = fn_or_handle[0];
context = fn_or_handle[1];
}
else {
fn = fn_or_handle;
context = ctxt;
}
// Iterate through the observers until something matches our
// function-context pair, then splice it out.
var observers = this.stream__observers;
for(var i = 0, length = observers.length; i !== length; i += 2) {
if(observers[i] === fn && observers[i + 1] === context) {
observers.splice(i, 2);
// Close the stream, because if there was a single observer,
// then the stream was open. Now that one observer has been
// removed, there are no observers.
if(length === 2) {
this.stream__active = false;
this.stream__close();
}
break;
}
}
},
// See stream.off_event_all
off_event_all: function() {
this.stream__observers = [];
this.stream__active = false;
this.stream__close();
},
// See stream.destroy
destroy: null
};
// For now, destroy is just an alias.
shared_subordinate_stream_prototype.destroy = shared_subordinate_stream_prototype.off_event_all;
/**
* produce_streamN_prototype(width)
*
* Produce a prototype for a Stream of the given width.
*
* @returns {Object}
*/
function produce_streamN_prototype(width) {
var args = arg_names.slice(0, width);
// Curried version of Function with all of the arguments bound to the
// args identifiers.
var function_args = Function.bind.apply(Function, [null].concat(args));
// The args identifiers in a comma list
var args_list = args.join(', ');
// The args identifiers with a comma preceding the first identifier.
// Empty string if width = 0.
var args_list_f = args.reduce(function (s, a) { return s + ', ' + a }, '');
var prototype = {
/**
* stream.emit(a, b, ...)
*
* Immediately emit a new event to all currently existing observers.
* Observers attached during emission will be ignored for this
* event, but will receive later events.
*
* The stream may be mutated during emission. Any alterations, such
* as adding or removing observers will not effect any ongoing
* emission, only successive events.
*/
emit: new function_args('\
"use strict";\
var observers = this.stream__observers.slice();\
for(var i = 0, length = observers.length; i !== length; i += 2) {\
var observer = observers[i];\
var context = observers[i + 1];\
observer.call(context' + args_list_f + ');\
}\
' ),
/**
* stream.map1(fn, context)
*
* Create a new subordinate stream of width 1 and map events from
* the superordinate stream to the subordinate stream by way of fn.
*
* @param {Function} fn Event mapping function. Should consume as
* many arguments as the superordinate stream's width. It's return
* value is used as the event emitted by the subordinate stream.
*
* @param {Object} context Context in which to call fn.
*
* @returns {SubordinateStream}
*/
map1: new Function('fn', 'context', '\
"use strict";\
var superordinate_stream = this;\
function map__observer(' + args_list + ') { this.emit(fn.call(context' + args_list_f + ')); };\
return new this.stream__stream_index[1](function map__open() {\
superordinate_stream.on_event(map__observer, this);\
}, function map__close() {\
superordinate_stream.off_event(map__observer, this);\
});\
' ),
/**
* stream.map(fn, context, width)
*
* Create a new subordinate stream of the given width and map events
* from the superordinate stream to the subordinate stream by way of
* fn.
*
* @param {Function} fn(a, b, ...) Event mapping function. Should
* consume as many arguments as the superordinate stream's width.
* It's return must be an array with a length of the subordinate
* stream's width.
*
* @param {Object} context Context in which to call fn.
*
* @param {Integer} width Width of the subordinate stream. If left
* undefined, the width is inherited.
*
* @returns {SubordinateStream}
*/
map: new Function('fn', 'context', 'width', '\
"use strict";\
var superordinate_stream = this;\
function map__observer(' + args_list + ') {\
this.emit.apply(this, fn.call(context' + args_list_f + '));\
};\
return new this.stream__stream_index[(width === undefined ? ' + width + ' : width)](function map__open() {\
superordinate_stream.on_event(map__observer, this);\
}, function map__close() {\
superordinate_stream.off_event(map__observer, this);\
});\
' ),
/**
* stream.mapfn(fn, context, width)
*
* Create a new subordinate stream of the given width and map events
* from the superordinate stream to the subordinate stream by way of
* fn.
*
* Experimental variation of map1 and map which uses a callback to
* emit events on the subordinate stream rather than the return
* value.
*
* @param {Function} fn(emit, a, b, ...) [description]
*
* @param {Object} context Context in which to call fn.
*
* @param {Integer} width Width of the subordinate stream. If left
* undefined, the width is inherited.
*
* @returns {SubordinateStream}
*/
mapfn: new Function('fn', 'context', 'width', '\
"use strict";\
var superordinate_stream = this;\
function mapfn__observer(' + args_list + ') {\
fn.call(context, this.stream__mapfn_emit' + args_list_f + ')\
}\
var stream = new this.stream__stream_index[(width === undefined ? ' + width + ' : width)](function mapfn__open() {\
superordinate_stream.on_event(mapfn__observer, this);\
}, function mapfn__close() {\
superordinate_stream.off_event(mapfn__observer, this);\
});\
stream.stream__mapfn_emit = function (' + args_list + ') {\
stream.emit(' + args_list + ');\
};\
return stream;\
' ),
/**
* stream.filter(fn, context)
*
* Create a new subordinate stream and re-emit events that are
* accepted by the filter function.
*
* @param {Function} fn Event filter function. Should consume as
* many arguments as the stream's width. It's return value will be
* cast to boolean, and if true the event will be re-emitted.
*
* @param {Object} context Context in which to call fn.
*
* @returns {SubordinateStream}
*/
filter: new Function('fn', 'context', '\
"use strict";\
var superordinate_stream = this;\
function filter__observer(' + args_list + ') { if(fn.call(context' + args_list_f + ') === true) this.emit(' + args_list + '); };\
return new this.stream__stream_index[' + width + '](function filter__open() {\
superordinate_stream.on_event(filter__observer, this);\
}, function filter__close() {\
superordinate_stream.off_event(filter__observer, this);\
});\
' ),
/**
* stream.to_property(a, b, ...)
*
* Create a new subordinate property, which consumes and re-emits
* events. The initial values of the property is defined in the
* arguments passed to this function.
*
* @returns {SubordinateProperty}
*/
to_property: new function_args('\
"use strict";\
var superordinate_stream = this;\
return new this.stream__property_index[' + width + '](function to_property__open() {\
superordinate_stream.on_event(this.emit, this);\
}, function to_property__close() {\
superordinate_stream.off_event(this.emit, this);\
}' + args_list_f + ');\
' ),
};
return extend(prototype, shared_stream_prototype);
}
/**
* produce_subordinate_streamN_prototype(streamN_prototype, width)
*
* Produce a prototype for a SubordinateStream of the given width.
*
* Because this prototype requires many of the functions provided by a
* regular Stream's prototype, streamN_prototype must be provided.
*
* @returns {Object}
*/
function produce_subordinate_streamN_prototype(streamN_prototype, width) {
return extend({}, streamN_prototype, shared_subordinate_stream_prototype);
}
// Prototype shared by all Property objects regardless of width.
var shared_property_prototype = {
property__stream_index: subordinate_stream_index,
property__property_index: subordinate_property_index,
/**
* property.off_event(fn, context)
*
* Remove an observer from the property. Only one instance of fn and
* context are removed from the property.
*
* @param {Function} fn Event callback function.
* @param {Object} context Context in which to call fn.
*/
off_event: function(fn_or_handle, ctxt) {
var fn, context;
if(fn_or_handle instanceof Array) {
fn = fn_or_handle[0];
context = fn_or_handle[1];
}
else {
fn = fn_or_handle;
context = ctxt;
}
var observers = this.property__observers;
for(var i = 0, length = observers.length; i !== length; i += 2) {
if(observers[i] === fn && observers[i + 1] === context) {
observers.splice(i, 2);
break;
}
}
},
/**
* property.off_event_all()
*
* Remove all observers from the property.
*/
off_event_all: function() {
this.property__observers = [];
},
/**
* property.to_property()
*
* Convert the property or stream into a property. When called on a
* stream, a new object may be created. When called on properties the
* property is returned.
*
* @returns {this}
*/
to_property: function () {
return this;
},
/**
* property.destroy()
*
* Destroy the property, removing all observers and any associated
* metadata.
*/
destroy: null
};
// For now, destroy is just an alias.
shared_property_prototype.destroy = shared_property_prototype.off_event_all;
/**
* produce_propertyN_prototype(width)
*
* Produce a prototype for a property of the given width.
*
* @returns {Object}
*/
function produce_propertyN_prototype(width) {
var args = arg_names.slice(0, width);
// Curried version of Function with all of the arguments bound to the
// args identifiers.
var function_args = Function.bind.apply(Function, [null].concat(args));
// Curried version of Function with the arguments bound to a 'fn' and
// 'context' followed by all of the args identifiers.
var function_fn_args = Function.bind.apply(Function, [null, 'fn', 'context'].concat(args));
// The args identifiers in a comma list
var args_list = args.join(', ');
// The args identifiers with a comma preceding the first identifier.
// Empty string if width = 0.
var args_list_f = args.reduce(function (s, a) { return s + ', ' + a }, '');
// The args identifiers prefixed with prev_ with a comma preceding the
// first identifier. Empty string if width = 0.
var args_list_prev_f = args.reduce(function (s, a) { return s + ', prev_' + a }, '');
// A list of nulls with a comma preceding the first null. Empty string
// if width = 0.
var null_list_f = args.reduce(function (s, a) { return s + ', null' }, '');
// The args identifiers prefixed with 'this.property__' with a comma
// preceding the first identifier. Empty string if width = 0.
var current_values_f = args.reduce(function (s, a) { return s + ', this.property__' + a }, '');
// Code fragment which saves previous values from 'this.property__' to
// 'prev_'.
var save_values = args.reduce(function (s, a) { return s + 'var prev_' + a + ' = this.property__' + a + ';' }, '');
// Code fragment which saves the args identifiers to 'this.property__'.
var write_values = args.reduce(function (s, a) { return s + 'this.property__' + a + ' = ' + a + ';' }, '');
var prototype = {
/**
* property.on_event(fn, context)
*
* Attach a new observer to the property, and return a handle for
* later removal. Observers will be called immediately after
* attachment with the current values. Then they will be called for
* each new event in the order that they were attached.
*
* @param {Function} fn Event callback function. Should consume
* twice as many arguments as the Property's width. The first set
* are the current values, the second set are the previous values.
*
* @param {Object} context Context in which to call fn.
*
* @returns {Handle} Handle of callback binding.
*/
on_event: new Function('fn', 'context', '\
"use strict";\
this.property__observers.push(fn, context);\
fn.call(context' + current_values_f + null_list_f + ');\
return [fn, context];\
' ),
/**
* property.emit(a, b, ...)
*
* Update the current values, then immediately emit a new event to
* all currently existing observers. Observers attached during
* emission will be ignored for this event, but will receive later
* events.
*
* The property may be mutated during emission. Any alterations, such
* as adding or removing observers will not effect any ongoing
* emission, only successive events.
*/
emit: new function_args('\
"use strict";\
' + save_values + '\
' + write_values + '\
var observers = this.property__observers.slice();\
for(var i = 0, length = observers.length; i !== length; i += 2) {\
var observer = observers[i];\
var context = observers[i + 1];\
observer.call(context' + args_list_f + args_list_prev_f + ');\
}\
' ),
/**
* property.map1(fn, context)
*
* Create a new subordinate property of width 1 and map events from
* the superordinate property to the subordinate property by way of
* fn.
*
* @param {Function} fn Event mapping function. Should consume
* twice as many arguments as the Property's width. The first set
* are the current values, the second set are the previous values.
* Should consume as It's return value is used as the event emitted
* by the subordinate property.
*
* @param {Object} context Context in which to call fn.
*
* @returns {SubordinateProperty}
*/
map1: new Function('fn', 'context', '\
"use strict";\
var superordinate_property = this;\
function map__observer(' + args_list + args_list_prev_f + ') {\
this.emit(fn.call(context' + args_list_f + args_list_prev_f + '));\
};\
return new this.property__property_index[1](function map__open() {\
superordinate_property.on_event(map__observer, this);\
}, function map__close() {\
superordinate_property.off_event(map__observer, this);\
});\
' ),
/**
* property.map(fn, context, width)
*
* Create a new subordinate property of the given width and map
* events from the superordinate property to the subordinate
* property by way of fn.
*
* @param {Function} fn Event mapping function. Should consume
* twice as many arguments as the Property's width. The first set
* are the current values, the second set are the previous values.
* It's return must be an array with a length of the subordinate
* property's width.
*
* @param {Object} context Context in which to call fn.
*
* @param {Integer} width Width of the subordinate stream. If left
* undefined, the width is inherited.
*
* @returns {SubordinateProperty}
*/
map: new Function('fn', 'context', 'width', '\
"use strict";\
var superordinate_property = this;\
function map__observer(' + args_list + args_list_prev_f + ') {\
this.emit.apply(this, fn.call(context' + args_list_f + args_list_prev_f + '));\
};\
return new this.property__property_index[(width === undefined ? ' + width + ' : width)](function map__open() {\
superordinate_property.on_event(map__observer, this);\
}, function map__close() {\
superordinate_property.off_event(map__observer, this);\
});\
' ),
/**
* property.mapfn(fn, context, width)
*
* Create a new subordinate property of the given width and map events
* from the superordinate property to the subordinate property by way of
* fn.
*
* Experimental variation of map1 and map which uses a callback to
* emit events on the subordinate property rather than the return
* value.
*
* @param {Function} fn(emit, a, b, ..., prev_a, prev_b, ...)
*
* @param {Object} context Context in which to call fn.
*
* @param {Integer} width Width of the subordinate property. If left
* undefined, the width is inherited.
*
* @returns {SubordinateStream}
*/
mapfn: new Function('fn', 'context', 'width', '\
"use strict";\
var superordinate_property = this;\
function mapfn__observer(' + args_list + args_list_prev_f + ') {\
fn.call(context, this.stream__mapfn_emit' + args_list_f + args_list_prev_f + ')\
}\
var property = new this.property__property_index[(width === undefined ? ' + width + ' : width)](function mapfn__open() {\
superordinate_property.on_event(mapfn__observer, this);\
}, function mapfn__close() {\
superordinate_property.off_event(mapfn__observer, this);\
});\
property.stream__mapfn_emit = function (' + args_list + ') {\
property.emit(' + args_list + ');\
};\
return property;\
' ),
/**
* property.filter(fn, context, a, b, ...)
*
* Create a new subordinate property and re-emit events that are
* accepted by the filter function.
*
* The a, b, ... arguments are used to initialize the subordinate
* property in-case the initial value is rejected by the filter
* function.
*
* @param {Function} fn Event filter function. Should consume
* twice as many arguments as the Property's width. The first set
* are the current values, the second set are the previous values.
* It's return value will be cast to boolean, and if true the event
* will be re-emitted.
*
* @param {Object} context Context in which to call fn.
*
* @returns {SubordinateProperty}
*/
filter: new function_fn_args('\
"use strict";\
var superordinate_property = this;\
function filter__observer(' + args_list + args_list_prev_f + ') {\
if(fn.call(context' + args_list_f + args_list_prev_f + ') === true) this.emit(' + args_list + ');\
};\
return new this.property__property_index[' + width + '](function filter__open() {\
superordinate_property.on_event(filter__observer, this);\
}, function filter__close() {\
superordinate_property.off_event(filter__observer, this);\
}' + args_list_f + ');\
' ),
/**
* property.to_stream()
*
* Create a new subordinate stream, which consumes and re-emits
* events. The current values are discarded.
*/
to_stream: new Function('\
"use strict";\
var superordinate_property = this;\
return new this.property__stream_index[' + width + '](function to_stream__open() {\
superordinate_property.on_event(this.emit, this);\
}, function to_stream__close() {\
superoridnate_property.off_event(this.emit, this);\
});\
' ),
};
return extend(prototype, shared_property_prototype);
}
/**
* produce_propertyN_constructor(width)
*
* Produce a constructor function for a property of the given width.
*
* This does not have a proper prototype set.
*
* @returns {Function}
*/
function produce_propertyN_constructor(width) {
var args = arg_names.slice(0, width);
var function_constructor = Function.bind.apply(Function, [null].concat(args));
var write_values = args.reduce(function (s, a) { return s + 'this.property__' + a + ' = ' + a + ';' }, '');
/**
* Property(a, b, ...)
*
* Construct a new Property with the initial values as supplied.
*
* @returns {Property}
*/
return new function_constructor('\
"use strict";\
this.property__observers = [];\
' + write_values + '\
' );
}
// Prototype shared by all SubordinateProperty objects regardless of width.
var shared_subordinate_property_prototype = {
// See property.off_event
off_event: function(fn_or_handle, ctxt) {
// Decide if we're being called with a handle or a function-context
// pair.
var fn, context;
if(fn_or_handle instanceof Array) {
fn = fn_or_handle[0];
context = fn_or_handle[1];
}
else {
fn = fn_or_handle;
context = ctxt;
}
// Iterate through the observers until something matches our
// function-context pair, then splice it out.
var observers = this.property__observers;
for(var i = 0, length = observers.length; i !== length; i += 2) {
if(observers[i] === fn && observers[i + 1] === context) {
observers.splice(i, 2);
// Close the stream, because if there was a single observer,
// then the stream was open. Now that one observer has been
// removed, there are no observers.
if(length === 2) {
this.property__active = false;
this.property__close();
}
break;
}
}
},
// See property.off_event_all
off_event_all: function () {
this.property__observers = [];
this.property__active = false;
this.property__close();
},
// See property.to_property
to_property: function () {
return this;
},
// See property.destroy
destroy: null
};
// For now, destroy is just an alias.
shared_subordinate_property_prototype.destroy = shared_subordinate_property_prototype.off_event_all;
/**
* produce_subordinate_propertyN_prototype(propertyN_prototype, width)
*
* Produce a prototype for a SubordinateProperty of the given width.
*
* Because this prototype requires many of the functions provided by a
* regular Property's prototype, propertyN_prototype must be provided.
*
* @returns {Object}
*/
function produce_subordinate_propertyN_prototype(propertyN_prototype, width) {
var args = arg_names.slice(0, width);
// The args identifiers prefixed with 'this.property__' with a comma
// preceding the first identifier. Empty string if width = 0.
var current_values_f = args.reduce(function (s, a) { return s + ', this.property__' + a }, '');
var prototype = {
// See property.on_event
on_event: new Function('fn', 'context', '\
"use strict";\
if(this.property__active === false) {\
this.property__active = true;\
this.property__open();\
}\
this.property__observers.push(fn, context);\
fn.call(context' + current_values_f + ');\
return [fn, context];\
' ),
};
return extend({}, propertyN_prototype, prototype, shared_subordinate_property_prototype);
}
/**
* produce_subordinate_propertyN_constructor(width)
*
* Produce a constructor function for a SubordinateProperty of the given
* width.
*
* This does not have a proper prototype set.
*
* @returns {Function}
*/
function produce_subordinate_propertyN_constructor(width) {
var args = arg_names.slice(0, width);
var function_constructor = Function.bind.apply(Function, [null, 'open', 'close'].concat(args));
var write_values = args.reduce(function (s, a) { return s + 'this.property__' + a + ' = ' + a + ';' }, '');
return new function_constructor('\
"use strict";\
this.property__observers = [];\
' + write_values + '\
this.property__active = false;\
this.property__open = open;\
this.property__close = close;\
' );
}
// Construct multiple variants of stream and property.
for(var N = 0; N !== 7; N++) {
var StreamN = function () {
this.stream__observers = [];
};
var stream_n_prototype = produce_streamN_prototype(N)
extend(StreamN.prototype, stream_n_prototype);
var SubordinateStreamN = function (open, close) {
this.stream__observers = [];
this.stream__active = false;
this.stream__open = open;
this.stream__close = close;
};
var subordinate_stream_n_prototype = produce_subordinate_streamN_prototype(stream_n_prototype, N);
extend(SubordinateStreamN.prototype, subordinate_stream_n_prototype);
var PropertyN = produce_propertyN_constructor(N);
var property_n_prototype = produce_propertyN_prototype(N);
extend(PropertyN.prototype, property_n_prototype);
var SubordinatePropertyN = produce_subordinate_propertyN_constructor(N);
var subordinate_property_n_prototype = produce_subordinate_propertyN_prototype(property_n_prototype, N);
extend(SubordinatePropertyN.prototype, subordinate_property_n_prototype);
exports["Stream" + N] = StreamN;
exports["SubordinateStream" + N] = SubordinateStreamN;
subordinate_stream_index[N] = SubordinateStreamN;
exports["Property" + N] = PropertyN;
exports["SubordinateProperty" + N] = SubordinatePropertyN;
subordinate_property_index[N] = SubordinatePropertyN;
}
})(this);
/**
* extend(target, [ mixin, [ mixin, ... ]])
*
* Copy all of the properties on each of the mixins onto the target.
*
* Each mixin is considered in order, properties on each mixin are copied on
* to the target. Later mixins will overwrite properties provided by earlier
* mixins.
*
* The object referenced by target is modified and returned.
*/
function extend(target, mixins) {
var len = arguments.length;
for(var i = 1; i !== len; i += 1) {
var mixin = arguments[i];
for(var prop in mixin) {
target[prop] = mixin[prop];
}
}
return target;
}
function log_bind() {
var args = [console];
var length = arguments.length;
for(var i = 0; i !== length; i += 1) args.push(arguments[i]);
return console.log.bind.apply(console.log, args);
}
///////// Tutorial /////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Create a new three-argument stream
var s3 = new Stream3();
// Observing a stream
var handle = s3.on_event(log_bind('event handler A'));
// Also observing a stream
var event_handler_b = log_bind('event handler B');
s3.on_event(event_handler_b, null);
// Removing an observer can be done using the handle
s3.off_event(handle);
// Or using the originally bound function and context
s3.off_event(event_handler_b, null);
s3.on_event(log_bind('event handler C'));
// To emit an event:
s3.emit(1, 2, 3);
// Since s3 is a three-argument stream it takes a maximum of three arguments.
// Extra arguments are ignored, and unbound arguments are passed as undefined.
s3.emit(1);
// Some higher order functions can be used:
var mult_s3 = s3.mapfn(function (emit, a, b, c) {
emit(a * b, b * c, c * a);
});
mult_s3.on_event(log_bind('multiplication handler'));
var filt_s3 = s3.filter(function (a, b, c) {
return (a + b + c) % 2;
});
filt_s3.on_event(log_bind('filtration handler'));
// Each creates a subordinate stream which observes events and re-emits them.
// But if the subordinate stream has no observers, then it doesn't observe
// events:
var dummy_s3 = s3.filter(function (a, b, c) {
console.error('Unbound filter called!');
});
s3.emit(2, 4, 6);
s3.emit(3, 5, 7);
s3.emit(4, 6, 8);
// Events can be manually emitted from subordinate streams:
mult_s3.emit(10, 20, 30);
// Properties are like streams, except they persist their last event, which they
// immediately emit to each new observer.
// Create a new two-argument property, and initialize it.
var p2 = new Property2('Hello', 5);
// They have the same interface as streams
p2.on_event(log_bind('property observer A'));
// Their observers, however receive the current value of the property along with
// whatever their previous value was.
p2.emit('Observer', 8);
// An example of a filter which emits events when the length of the current
// first value isn't the same as the previous second value.
var filt_p2 = p2.filter(function (str, len, prev_str, prev_len) {
return str.length !== prev_len;
});
filt_p2.on_event(log_bind('event string length incorrect'));
p2.emit('Watcher', 5);
p2.emit('Child', 0);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment