weaverjs
Version:
Concise and readable multitasking
1,663 lines (1,293 loc) • 46.7 kB
JavaScript
/*!
Copyright © 2016 Max Franz
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the “Software”), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.weaver = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
;
var is = _dereq_('./is');
var util = _dereq_('./util');
var Promise = _dereq_('./promise');
var Event = _dereq_('./event');
var define = {
// event function reusable stuff
event: {
regex: /(\w+)(\.\w+)?/, // regex for matching event strings (e.g. "click.namespace")
optionalTypeRegex: /(\w+)?(\.\w+)?/,
falseCallback: function(){ return false; }
},
// event binding
on: function( params ){
var defaults = {
unbindSelfOnTrigger: false,
unbindAllBindersOnTrigger: false
};
params = util.extend({}, defaults, params);
return function onImpl(events, data, callback){
var self = this;
var selfIsArrayLike = self.length !== undefined;
var all = selfIsArrayLike ? self : [self]; // put in array if not array-like
var eventsIsString = is.string(events);
var p = params;
if( is.fn(data) || data === false ){ // data is actually callback
callback = data;
data = undefined;
}
// if there isn't a callback, we can't really do anything
// (can't speak for mapped events arg version)
if( !(is.fn(callback) || callback === false) && eventsIsString ){
return self; // maintain chaining
}
if( eventsIsString ){ // then convert to map
var map = {};
map[ events ] = callback;
events = map;
}
for( var evts in events ){
callback = events[evts];
if( callback === false ){
callback = define.event.falseCallback;
}
if( !is.fn(callback) ){ continue; }
evts = evts.split(/\s+/);
for( var i = 0; i < evts.length; i++ ){
var evt = evts[i];
if( is.emptyString(evt) ){ continue; }
var match = evt.match( define.event.regex ); // type[.namespace]
if( match ){
var type = match[1];
var namespace = match[2] ? match[2] : undefined;
var listener = {
callback: callback, // callback to run
data: data, // extra data in eventObj.data
type: type, // the event type (e.g. 'click')
namespace: namespace, // the event namespace (e.g. ".foo")
unbindSelfOnTrigger: p.unbindSelfOnTrigger,
unbindAllBindersOnTrigger: p.unbindAllBindersOnTrigger,
binders: all // who bound together
};
for( var j = 0; j < all.length; j++ ){
var _p = all[j]._private;
_p.listeners = _p.listeners || [];
_p.listeners.push( listener );
}
}
} // for events array
} // for events map
return self; // maintain chaining
}; // function
}, // on
eventAliasesOn: function( proto ){
var p = proto;
p.addListener = p.listen = p.bind = p.on;
p.removeListener = p.unlisten = p.unbind = p.off;
p.emit = p.trigger;
// this is just a wrapper alias of .on()
p.pon = p.promiseOn = function( events, selector ){
var self = this;
var args = Array.prototype.slice.call( arguments, 0 );
return new Promise(function( resolve, reject ){
var callback = function( e ){
self.off.apply( self, offArgs );
resolve( e );
};
var onArgs = args.concat([ callback ]);
var offArgs = onArgs.concat([]);
self.on.apply( self, onArgs );
});
};
},
off: function offImpl( params ){
var defaults = {
};
params = util.extend({}, defaults, params);
return function(events, callback){
var self = this;
var selfIsArrayLike = self.length !== undefined;
var all = selfIsArrayLike ? self : [self]; // put in array if not array-like
var eventsIsString = is.string(events);
if( arguments.length === 0 ){ // then unbind all
for( var i = 0; i < all.length; i++ ){
all[i]._private.listeners = [];
}
return self; // maintain chaining
}
if( eventsIsString ){ // then convert to map
var map = {};
map[ events ] = callback;
events = map;
}
for( var evts in events ){
callback = events[evts];
if( callback === false ){
callback = define.event.falseCallback;
}
evts = evts.split(/\s+/);
for( var h = 0; h < evts.length; h++ ){
var evt = evts[h];
if( is.emptyString(evt) ){ continue; }
var match = evt.match( define.event.optionalTypeRegex ); // [type][.namespace]
if( match ){
var type = match[1] ? match[1] : undefined;
var namespace = match[2] ? match[2] : undefined;
for( var i = 0; i < all.length; i++ ){ //
var listeners = all[i]._private.listeners = all[i]._private.listeners || [];
for( var j = 0; j < listeners.length; j++ ){
var listener = listeners[j];
var nsMatches = !namespace || namespace === listener.namespace;
var typeMatches = !type || listener.type === type;
var cbMatches = !callback || callback === listener.callback;
var listenerMatches = nsMatches && typeMatches && cbMatches;
// delete listener if it matches
if( listenerMatches ){
listeners.splice(j, 1);
j--;
}
} // for listeners
} // for all
} // if match
} // for events array
} // for events map
return self; // maintain chaining
}; // function
}, // off
trigger: function( params ){
var defaults = {};
params = util.extend({}, defaults, params);
return function triggerImpl(events, extraParams, fnToTrigger){
var self = this;
var selfIsArrayLike = self.length !== undefined;
var all = selfIsArrayLike ? self : [self]; // put in array if not array-like
var eventsIsString = is.string(events);
var eventsIsObject = is.plainObject(events);
var eventsIsEvent = is.event(events);
if( eventsIsString ){ // then make a plain event object for each event name
var evts = events.split(/\s+/);
events = [];
for( var i = 0; i < evts.length; i++ ){
var evt = evts[i];
if( is.emptyString(evt) ){ continue; }
var match = evt.match( define.event.regex ); // type[.namespace]
var type = match[1];
var namespace = match[2] ? match[2] : undefined;
events.push( {
type: type,
namespace: namespace
} );
}
} else if( eventsIsObject ){ // put in length 1 array
var eventArgObj = events;
events = [ eventArgObj ];
}
if( extraParams ){
if( !is.array(extraParams) ){ // make sure extra params are in an array if specified
extraParams = [ extraParams ];
}
} else { // otherwise, we've got nothing
extraParams = [];
}
for( var i = 0; i < events.length; i++ ){ // trigger each event in order
var evtObj = events[i];
for( var j = 0; j < all.length; j++ ){ // for each
var triggerer = all[j];
var listeners = triggerer._private.listeners = triggerer._private.listeners || [];
var bubbleUp = false;
// create the event for this element from the event object
var evt;
if( eventsIsEvent ){ // then just get the object
evt = evtObj;
} else { // then we have to make one
evt = new Event( evtObj, {
namespace: evtObj.namespace
} );
}
if( fnToTrigger ){ // then override the listeners list with just the one we specified
listeners = [{
namespace: evt.namespace,
type: evt.type,
callback: fnToTrigger
}];
}
for( var k = 0; k < listeners.length; k++ ){ // check each listener
var lis = listeners[k];
var nsMatches = !lis.namespace || lis.namespace === evt.namespace;
var typeMatches = lis.type === evt.type;
var targetMatches = true;
var listenerMatches = nsMatches && typeMatches && targetMatches;
if( listenerMatches ){ // then trigger it
var args = [ evt ];
args = args.concat( extraParams ); // add extra params to args list
if( lis.data ){ // add on data plugged into binding
evt.data = lis.data;
} else { // or clear it in case the event obj is reused
evt.data = undefined;
}
if( lis.unbindSelfOnTrigger || lis.unbindAllBindersOnTrigger ){ // then remove listener
listeners.splice(k, 1);
k--;
}
if( lis.unbindAllBindersOnTrigger ){ // then delete the listener for all binders
var binders = lis.binders;
for( var l = 0; l < binders.length; l++ ){
var binder = binders[l];
if( !binder || binder === triggerer ){ continue; } // already handled triggerer or we can't handle it
var binderListeners = binder._private.listeners;
for( var m = 0; m < binderListeners.length; m++ ){
var binderListener = binderListeners[m];
if( binderListener === lis ){ // delete listener from list
binderListeners.splice(m, 1);
m--;
}
}
}
}
// run the callback
var context = triggerer;
var ret = lis.callback.apply( context, args );
if( ret === false || evt.isPropagationStopped() ){
// then don't bubble
bubbleUp = false;
if( ret === false ){
// returning false is a shorthand for stopping propagation and preventing the def. action
evt.stopPropagation();
evt.preventDefault();
}
}
} // if listener matches
} // for each listener
if( bubbleUp ){
// TODO if bubbling is supported...
}
} // for each of all
} // for each event
return self; // maintain chaining
}; // function
} // trigger
}; // define
module.exports = define;
},{"./event":2,"./is":5,"./promise":6,"./util":8}],2:[function(_dereq_,module,exports){
;
// https://github.com/jquery/jquery/blob/master/src/event.js
var Event = function( src, props ) {
// Allow instantiation without the 'new' keyword
if ( !(this instanceof Event) ) {
return new Event( src, props );
}
// Event object
if ( src && src.type ) {
this.originalEvent = src;
this.type = src.type;
// Events bubbling up the document may have been marked as prevented
// by a handler lower down the tree; reflect the correct value.
this.isDefaultPrevented = ( src.defaultPrevented ) ? returnTrue : returnFalse;
// Event type
} else {
this.type = src;
}
// Put explicitly provided properties onto the event object
if ( props ) {
// more efficient to manually copy fields we use
this.type = props.type !== undefined ? props.type : this.type;
this.namespace = props.namespace;
this.layout = props.layout;
this.data = props.data;
this.message = props.message;
}
// Create a timestamp if incoming event doesn't have one
this.timeStamp = src && src.timeStamp || +new Date();
};
function returnFalse() {
return false;
}
function returnTrue() {
return true;
}
// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
Event.prototype = {
instanceString: function(){ return 'event'; },
preventDefault: function() {
this.isDefaultPrevented = returnTrue;
var e = this.originalEvent;
if ( !e ) {
return;
}
// if preventDefault exists run it on the original event
if ( e.preventDefault ) {
e.preventDefault();
}
},
stopPropagation: function() {
this.isPropagationStopped = returnTrue;
var e = this.originalEvent;
if ( !e ) {
return;
}
// if stopPropagation exists run it on the original event
if ( e.stopPropagation ) {
e.stopPropagation();
}
},
stopImmediatePropagation: function() {
this.isImmediatePropagationStopped = returnTrue;
this.stopPropagation();
},
isDefaultPrevented: returnFalse,
isPropagationStopped: returnFalse,
isImmediatePropagationStopped: returnFalse
};
module.exports = Event;
},{}],3:[function(_dereq_,module,exports){
/*! Weaver licensed under MIT (https://tldrlegal.com/license/mit-license), copyright Max Franz */
;
var is = _dereq_('./is');
var util = _dereq_('./util');
var Thread = _dereq_('./thread');
var Promise = _dereq_('./promise');
var define = _dereq_('./define');
var Fabric = function( N ){
if( !(this instanceof Fabric) ){
return new Fabric( N );
}
this._private = {
pass: []
};
var defN = 4;
if( is.number(N) ){
// then use the specified number of threads
} if( typeof navigator !== 'undefined' && navigator.hardwareConcurrency != null ){
N = navigator.hardwareConcurrency;
} else {
try{
N = _dereq_('os').cpus().length;
} catch( err ){
N = defN;
}
} // TODO could use an estimation here but would the additional expense be worth it?
for( var i = 0; i < N; i++ ){
this[i] = new Thread();
}
this.length = N;
};
var fabfn = Fabric.prototype; // short alias
util.extend(fabfn, {
instanceString: function(){ return 'fabric'; },
// require fn in all threads
require: function( fn, as ){
for( var i = 0; i < this.length; i++ ){
var thread = this[i];
thread.require( fn, as );
}
return this;
},
// get a random thread
random: function(){
var i = Math.round( (this.length - 1) * Math.random() );
var thread = this[i];
return thread;
},
// run on random thread
run: function( fn ){
var pass = this._private.pass.shift();
return this.random().pass( pass ).run( fn );
},
// sends a random thread a message
message: function( m ){
return this.random().message( m );
},
// send all threads a message
broadcast: function( m ){
for( var i = 0; i < this.length; i++ ){
var thread = this[i];
thread.message( m );
}
return this; // chaining
},
// stop all threads
stop: function(){
for( var i = 0; i < this.length; i++ ){
var thread = this[i];
thread.stop();
}
return this; // chaining
},
// pass data to be used with .spread() etc.
pass: function( data ){
var pass = this._private.pass;
if( is.array(data) ){
pass.push( data );
} else {
throw 'Only arrays may be used with fabric.pass()';
}
return this; // chaining
},
spreadSize: function(){
var subsize = Math.ceil( this._private.pass[0].length / this.length );
subsize = Math.max( 1, subsize ); // don't pass less than one ele to each thread
return subsize;
},
// split the data into slices to spread the data equally among threads
spread: function( fn ){
var self = this;
var _p = self._private;
var subsize = self.spreadSize(); // number of pass eles to handle in each thread
var pass = _p.pass.shift().concat([]); // keep a copy
var runPs = [];
for( var i = 0; i < this.length; i++ ){
var thread = this[i];
var slice = pass.splice( 0, subsize );
var runP = thread.pass( slice ).run( fn );
runPs.push( runP );
var doneEarly = pass.length === 0;
if( doneEarly ){ break; }
}
return Promise.all( runPs ).then(function( thens ){
var postpass = [];
var p = 0;
// fill postpass with the total result joined from all threads
for( var i = 0; i < thens.length; i++ ){
var then = thens[i]; // array result from thread i
for( var j = 0; j < then.length; j++ ){
var t = then[j]; // array element
postpass[ p++ ] = t;
}
}
return postpass;
});
},
// parallel version of array.map()
map: function( fn ){
var self = this;
self.require( fn, '_$_$_fabmap' );
return self.spread(function( split ){
var mapped = [];
var origResolve = resolve; // jshint ignore:line
resolve = function( val ){ // jshint ignore:line
mapped.push( val );
};
for( var i = 0; i < split.length; i++ ){
var oldLen = mapped.length;
var ret = _$_$_fabmap( split[i] ); // jshint ignore:line
var nothingInsdByResolve = oldLen === mapped.length;
if( nothingInsdByResolve ){
mapped.push( ret );
}
}
resolve = origResolve; // jshint ignore:line
return mapped;
});
},
// parallel version of array.filter()
filter: function( fn ){
var _p = this._private;
var pass = _p.pass[0];
return this.map( fn ).then(function( include ){
var ret = [];
for( var i = 0; i < pass.length; i++ ){
var datum = pass[i];
var incDatum = include[i];
if( incDatum ){
ret.push( datum );
}
}
return ret;
});
},
// sorts the passed array using a divide and conquer strategy
sort: function( cmp ){
var self = this;
var P = this._private.pass[0].length;
var subsize = this.spreadSize();
cmp = cmp || function( a, b ){ // default comparison function
if( a < b ){
return -1;
} else if( a > b ){
return 1;
}
return 0;
};
self.require( cmp, '_$_$_cmp' );
return self.spread(function( split ){ // sort each split normally
var sortedSplit = split.sort( _$_$_cmp ); // jshint ignore:line
resolve( sortedSplit ); // jshint ignore:line
}).then(function( joined ){
// do all the merging in the main thread to minimise data transfer
// TODO could do merging in separate threads but would incur add'l cost of data transfer
// for each level of the merge
var merge = function( i, j, max ){
// don't overflow array
j = Math.min( j, P );
max = Math.min( max, P );
// left and right sides of merge
var l = i;
var r = j;
var sorted = [];
for( var k = l; k < max; k++ ){
var eleI = joined[i];
var eleJ = joined[j];
if( i < r && ( j >= max || cmp(eleI, eleJ) <= 0 ) ){
sorted.push( eleI );
i++;
} else {
sorted.push( eleJ );
j++;
}
}
// in the array proper, put the sorted values
for( var k = 0; k < sorted.length; k++ ){ // kth sorted item
var index = l + k;
joined[ index ] = sorted[k];
}
};
for( var splitL = subsize; splitL < P; splitL *= 2 ){ // merge until array is "split" as 1
for( var i = 0; i < P; i += 2*splitL ){
merge( i, i + splitL, i + 2*splitL );
}
}
return joined;
});
}
});
var defineRandomPasser = function( opts ){
opts = opts || {};
return function( fn, arg1 ){
var pass = this._private.pass.shift();
return this.random().pass( pass )[ opts.threadFn ]( fn, arg1 );
};
};
util.extend(fabfn, {
randomMap: defineRandomPasser({ threadFn: 'map' }),
reduce: defineRandomPasser({ threadFn: 'reduce' }),
reduceRight: defineRandomPasser({ threadFn: 'reduceRight' })
});
// aliases
var fn = fabfn;
fn.promise = fn.run;
fn.terminate = fn.halt = fn.stop;
fn.include = fn.require;
// pull in event apis
util.extend(fabfn, {
on: define.on(),
one: define.on({ unbindSelfOnTrigger: true }),
off: define.off(),
trigger: define.trigger()
});
define.eventAliasesOn( fabfn );
module.exports = Fabric;
},{"./define":1,"./is":5,"./promise":6,"./thread":7,"./util":8,"os":undefined}],4:[function(_dereq_,module,exports){
;
var Thread = _dereq_('./thread');
var Fabric = _dereq_('./fabric');
var weaver = function(){ // jshint ignore:line
return;
};
weaver.version = '1.2.0';
weaver.thread = weaver.Thread = weaver.worker = weaver.Worker = Thread;
weaver.fabric = weaver.Fabric = Fabric;
module.exports = weaver;
},{"./fabric":3,"./thread":7}],5:[function(_dereq_,module,exports){
// type testing utility functions
;
var typeofstr = typeof '';
var typeofobj = typeof {};
var typeoffn = typeof function(){};
var instanceStr = function( obj ){
return obj && obj.instanceString && is.fn( obj.instanceString ) ? obj.instanceString() : null;
};
var is = {
defined: function(obj){
return obj != null; // not undefined or null
},
string: function(obj){
return obj != null && typeof obj == typeofstr;
},
fn: function(obj){
return obj != null && typeof obj === typeoffn;
},
array: function(obj){
return Array.isArray ? Array.isArray(obj) : obj != null && obj instanceof Array;
},
plainObject: function(obj){
return obj != null && typeof obj === typeofobj && !is.array(obj) && obj.constructor === Object;
},
object: function(obj){
return obj != null && typeof obj === typeofobj;
},
number: function(obj){
return obj != null && typeof obj === typeof 1 && !isNaN(obj);
},
integer: function( obj ){
return is.number(obj) && Math.floor(obj) === obj;
},
bool: function(obj){
return obj != null && typeof obj === typeof true;
},
event: function(obj){
return instanceStr(obj) === 'event';
},
thread: function(obj){
return instanceStr(obj) === 'thread';
},
fabric: function(obj){
return instanceStr(obj) === 'fabric';
},
emptyString: function(obj){
if( !obj ){ // null is empty
return true;
} else if( is.string(obj) ){
if( obj === '' || obj.match(/^\s+$/) ){
return true; // empty string is empty
}
}
return false; // otherwise, we don't know what we've got
},
nonemptyString: function(obj){
if( obj && is.string(obj) && obj !== '' && !obj.match(/^\s+$/) ){
return true;
}
return false;
}
};
module.exports = is;
},{}],6:[function(_dereq_,module,exports){
// internal, minimal Promise impl s.t. apis can return promises in old envs
// based on thenable (http://github.com/rse/thenable)
;
/* promise states [Promises/A+ 2.1] */
var STATE_PENDING = 0; /* [Promises/A+ 2.1.1] */
var STATE_FULFILLED = 1; /* [Promises/A+ 2.1.2] */
var STATE_REJECTED = 2; /* [Promises/A+ 2.1.3] */
/* promise object constructor */
var api = function (executor) {
/* optionally support non-constructor/plain-function call */
if (!(this instanceof api))
return new api(executor);
/* initialize object */
this.id = "Thenable/1.0.7";
this.state = STATE_PENDING; /* initial state */
this.fulfillValue = undefined; /* initial value */ /* [Promises/A+ 1.3, 2.1.2.2] */
this.rejectReason = undefined; /* initial reason */ /* [Promises/A+ 1.5, 2.1.3.2] */
this.onFulfilled = []; /* initial handlers */
this.onRejected = []; /* initial handlers */
/* provide optional information-hiding proxy */
this.proxy = {
then: this.then.bind(this)
};
/* support optional executor function */
if (typeof executor === "function")
executor.call(this, this.fulfill.bind(this), this.reject.bind(this));
};
/* promise API methods */
api.prototype = {
/* promise resolving methods */
fulfill: function (value) { return deliver(this, STATE_FULFILLED, "fulfillValue", value); },
reject: function (value) { return deliver(this, STATE_REJECTED, "rejectReason", value); },
/* "The then Method" [Promises/A+ 1.1, 1.2, 2.2] */
then: function (onFulfilled, onRejected) {
var curr = this;
var next = new api(); /* [Promises/A+ 2.2.7] */
curr.onFulfilled.push(
resolver(onFulfilled, next, "fulfill")); /* [Promises/A+ 2.2.2/2.2.6] */
curr.onRejected.push(
resolver(onRejected, next, "reject" )); /* [Promises/A+ 2.2.3/2.2.6] */
execute(curr);
return next.proxy; /* [Promises/A+ 2.2.7, 3.3] */
}
};
/* deliver an action */
var deliver = function (curr, state, name, value) {
if (curr.state === STATE_PENDING) {
curr.state = state; /* [Promises/A+ 2.1.2.1, 2.1.3.1] */
curr[name] = value; /* [Promises/A+ 2.1.2.2, 2.1.3.2] */
execute(curr);
}
return curr;
};
/* execute all handlers */
var execute = function (curr) {
if (curr.state === STATE_FULFILLED)
execute_handlers(curr, "onFulfilled", curr.fulfillValue);
else if (curr.state === STATE_REJECTED)
execute_handlers(curr, "onRejected", curr.rejectReason);
};
/* execute particular set of handlers */
var execute_handlers = function (curr, name, value) {
/* global setImmediate: true */
/* global setTimeout: true */
/* short-circuit processing */
if (curr[name].length === 0)
return;
/* iterate over all handlers, exactly once */
var handlers = curr[name];
curr[name] = []; /* [Promises/A+ 2.2.2.3, 2.2.3.3] */
var func = function () {
for (var i = 0; i < handlers.length; i++)
handlers[i](value); /* [Promises/A+ 2.2.5] */
};
/* execute procedure asynchronously */ /* [Promises/A+ 2.2.4, 3.1] */
if (typeof setImmediate === "function")
setImmediate(func);
else
setTimeout(func, 0);
};
/* generate a resolver function */
var resolver = function (cb, next, method) {
return function (value) {
if (typeof cb !== "function") /* [Promises/A+ 2.2.1, 2.2.7.3, 2.2.7.4] */
next[method].call(next, value); /* [Promises/A+ 2.2.7.3, 2.2.7.4] */
else {
var result;
try { result = cb(value); } /* [Promises/A+ 2.2.2.1, 2.2.3.1, 2.2.5, 3.2] */
catch (e) {
next.reject(e); /* [Promises/A+ 2.2.7.2] */
return;
}
resolve(next, result); /* [Promises/A+ 2.2.7.1] */
}
};
};
/* "Promise Resolution Procedure" */ /* [Promises/A+ 2.3] */
var resolve = function (promise, x) {
/* sanity check arguments */ /* [Promises/A+ 2.3.1] */
if (promise === x || promise.proxy === x) {
promise.reject(new TypeError("cannot resolve promise with itself"));
return;
}
/* surgically check for a "then" method
(mainly to just call the "getter" of "then" only once) */
var then;
if ((typeof x === "object" && x !== null) || typeof x === "function") {
try { then = x.then; } /* [Promises/A+ 2.3.3.1, 3.5] */
catch (e) {
promise.reject(e); /* [Promises/A+ 2.3.3.2] */
return;
}
}
/* handle own Thenables [Promises/A+ 2.3.2]
and similar "thenables" [Promises/A+ 2.3.3] */
if (typeof then === "function") {
var resolved = false;
try {
/* call retrieved "then" method */ /* [Promises/A+ 2.3.3.3] */
then.call(x,
/* resolvePromise */ /* [Promises/A+ 2.3.3.3.1] */
function (y) {
if (resolved) return; resolved = true; /* [Promises/A+ 2.3.3.3.3] */
if (y === x) /* [Promises/A+ 3.6] */
promise.reject(new TypeError("circular thenable chain"));
else
resolve(promise, y);
},
/* rejectPromise */ /* [Promises/A+ 2.3.3.3.2] */
function (r) {
if (resolved) return; resolved = true; /* [Promises/A+ 2.3.3.3.3] */
promise.reject(r);
}
);
}
catch (e) {
if (!resolved) /* [Promises/A+ 2.3.3.3.3] */
promise.reject(e); /* [Promises/A+ 2.3.3.3.4] */
}
return;
}
/* handle other values */
promise.fulfill(x); /* [Promises/A+ 2.3.4, 2.3.3.4] */
};
// use native promises where possible
var Promise = typeof Promise === 'undefined' ? api : Promise;
// so we always have Promise.all()
Promise.all = Promise.all || function( ps ){
return new Promise(function( resolveAll, rejectAll ){
var vals = new Array( ps.length );
var doneCount = 0;
var fulfill = function( i, val ){
vals[i] = val;
doneCount++;
if( doneCount === ps.length ){
resolveAll( vals );
}
};
for( var i = 0; i < ps.length; i++ ){
(function( i ){
var p = ps[i];
var isPromise = p.then != null;
if( isPromise ){
p.then(function( val ){
fulfill( i, val );
}, function( err ){
rejectAll( err );
});
} else {
var val = p;
fulfill( i, val );
}
})( i );
}
});
};
module.exports = Promise;
},{}],7:[function(_dereq_,module,exports){
/*! Weaver licensed under MIT (https://tldrlegal.com/license/mit-license), copyright Max Franz */
// cross-env thread/worker
// NB : uses (heavyweight) processes on nodejs so best not to create too many threads
;
var window = _dereq_('./window');
var util = _dereq_('./util');
var Promise = _dereq_('./promise');
var Event = _dereq_('./event');
var define = _dereq_('./define');
var is = _dereq_('./is');
var Thread = function( opts ){
if( !(this instanceof Thread) ){
return new Thread( opts );
}
var _p = this._private = {
requires: [],
files: [],
queue: null,
pass: [],
disabled: false
};
if( is.plainObject(opts) ){
if( opts.disabled != null ){
_p.disabled = !!opts.disabled;
}
}
};
var thdfn = Thread.prototype; // short alias
var stringifyFieldVal = function( val ){
var valStr = is.fn( val ) ? val.toString() : "JSON.parse('" + JSON.stringify(val) + "')";
return valStr;
};
// allows for requires with prototypes and subobjs etc
var fnAsRequire = function( fn ){
var req;
var fnName;
if( is.object(fn) && fn.fn ){ // manual fn
req = fnAs( fn.fn, fn.name );
fnName = fn.name;
fn = fn.fn;
} else if( is.fn(fn) ){ // auto fn
req = fn.toString();
fnName = fn.name;
} else if( is.string(fn) ){ // stringified fn
req = fn;
} else if( is.object(fn) ){ // plain object
if( fn.proto ){
req = '';
} else {
req = fn.name + ' = {};';
}
fnName = fn.name;
fn = fn.obj;
}
req += '\n';
var protoreq = function( val, subname ){
if( val.prototype ){
var protoNonempty = false;
for( var prop in val.prototype ){ protoNonempty = true; break; } // jshint ignore:line
if( protoNonempty ){
req += fnAsRequire( {
name: subname,
obj: val,
proto: true
}, val );
}
}
};
// pull in prototype
if( fn.prototype && fnName != null ){
for( var name in fn.prototype ){
var protoStr = '';
var val = fn.prototype[ name ];
var valStr = stringifyFieldVal( val );
var subname = fnName + '.prototype.' + name;
protoStr += subname + ' = ' + valStr + ';\n';
if( protoStr ){
req += protoStr;
}
protoreq( val, subname ); // subobject with prototype
}
}
// pull in properties for obj/fns
if( !is.string(fn) ){ for( var name in fn ){
var propsStr = '';
if( fn.hasOwnProperty(name) ){
var val = fn[ name ];
var valStr = stringifyFieldVal( val );
var subname = fnName + '["' + name + '"]';
propsStr += subname + ' = ' + valStr + ';\n';
}
if( propsStr ){
req += propsStr;
}
protoreq( val, subname ); // subobject with prototype
} }
return req;
};
var isPathStr = function( str ){
return is.string(str) && str.match(/\.js$/);
};
util.extend(thdfn, {
instanceString: function(){ return 'thread'; },
require: function( fn, as ){
var requires = this._private.requires;
if( isPathStr(fn) ){
this._private.files.push( fn );
return this;
}
if( as ){
if( is.fn(fn) ){
fn = { name: as, fn: fn };
} else {
fn = { name: as, obj: fn };
}
} else {
if( is.fn(fn) ){
if( !fn.name ){
throw 'The function name could not be automatically determined. Use thread.require( someFunction, "someFunction" )';
}
fn = { name: fn.name, fn: fn };
}
}
requires.push( fn );
return this; // chaining
},
pass: function( data ){
this._private.pass.push( data );
return this; // chaining
},
run: function( fn, pass ){ // fn used like main()
var self = this;
var _p = this._private;
pass = pass || _p.pass.shift();
if( _p.stopped ){
throw 'Attempted to run a stopped thread! Start a new thread or do not stop the existing thread and reuse it.';
}
if( _p.running ){
return ( _p.queue = _p.queue.then(function(){ // inductive step
return self.run( fn, pass );
}) );
}
var useWW = window != null && !_p.disabled;
var useNode = !window && typeof module !== 'undefined' && !_p.disabled;
self.trigger('run');
var runP = new Promise(function( resolve, reject ){
_p.running = true;
var threadTechAlreadyExists = _p.ran;
var fnImplStr = is.string( fn ) ? fn : fn.toString();
// worker code to exec
var fnStr = '\n' + ( _p.requires.map(function( r ){
return fnAsRequire( r );
}) ).concat( _p.files.map(function( f ){
if( useWW ){
var wwifyFile = function( file ){
if( file.match(/^\.\//) || file.match(/^\.\./) ){
return window.location.origin + window.location.pathname + file;
} else if( file.match(/^\//) ){
return window.location.origin + '/' + file;
}
return file;
};
return 'importScripts("' + wwifyFile(f) + '");';
} else if( useNode ) {
return 'eval( require("fs").readFileSync("' + f + '", { encoding: "utf8" }) );';
} else {
throw 'External file `' + f + '` can not be required without any threading technology.';
}
}) ).concat([
'( function(){',
'var ret = (' + fnImplStr + ')(' + JSON.stringify(pass) + ');',
'if( ret !== undefined ){ resolve(ret); }', // assume if ran fn returns defined value (incl. null), that we want to resolve to it
'} )()\n'
]).join('\n');
// because we've now consumed the requires, empty the list so we don't dupe on next run()
_p.requires = [];
_p.files = [];
if( useWW ){
var fnBlob, fnUrl;
// add normalised thread api functions
if( !threadTechAlreadyExists ){
var fnPre = fnStr + '';
fnStr = [
'function _ref_(o){ return eval(o); };',
'function broadcast(m){ return message(m); };', // alias
'function message(m){ postMessage(m); };',
'function listen(fn){',
' self.addEventListener("message", function(m){ ',
' if( typeof m === "object" && (m.data.$$eval || m.data === "$$start") ){',
' } else { ',
' fn( m.data );',
' }',
' });',
'};',
'self.addEventListener("message", function(m){ if( m.data.$$eval ){ eval( m.data.$$eval ); } });',
'function resolve(v){ postMessage({ $$resolve: v }); };',
'function reject(v){ postMessage({ $$reject: v }); };'
].join('\n');
fnStr += fnPre;
fnBlob = new Blob([ fnStr ], {
type: 'application/javascript'
});
fnUrl = window.URL.createObjectURL( fnBlob );
}
// create webworker and let it exec the serialised code
var ww = _p.webworker = _p.webworker || new Worker( fnUrl );
if( threadTechAlreadyExists ){ // then just exec new run() code
ww.postMessage({
$$eval: fnStr
});
}
// worker messages => events
var cb;
ww.addEventListener('message', cb = function( m ){
var isObject = is.object(m) && is.object( m.data );
if( isObject && ('$$resolve' in m.data) ){
ww.removeEventListener('message', cb); // done listening b/c resolve()
resolve( m.data.$$resolve );
} else if( isObject && ('$$reject' in m.data) ){
ww.removeEventListener('message', cb); // done listening b/c reject()
reject( m.data.$$reject );
} else {
self.trigger( new Event(m, { type: 'message', message: m.data }) );
}
}, false);
if( !threadTechAlreadyExists ){
ww.postMessage('$$start'); // start up the worker
}
} else if( useNode ){
// create a new process
if( !_p.child ){
_p.child = ( _dereq_('child_process').fork( _dereq_('path').join(__dirname, 'thread-node-fork') ) );
}
var child = _p.child;
// child process messages => events
var cb;
child.on('message', cb = function( m ){
if( is.object(m) && ('$$resolve' in m) ){
child.removeListener('message', cb); // done listening b/c resolve()
resolve( m.$$resolve );
} else if( is.object(m) && ('$$reject' in m) ){
child.removeListener('message', cb); // done listening b/c reject()
reject( m.$$reject );
} else {
self.trigger( new Event({}, { type: 'message', message: m }) );
}
});
// ask the child process to eval the worker code
child.send({
$$eval: fnStr
});
} else { // use a fallback mechanism using a timeout
var promiseResolve = resolve;
var promiseReject = reject;
var timer = _p.timer = _p.timer || {
listeners: [],
exec: function(){
// as a string so it can't be mangled by minifiers and processors
fnStr = [
'function _ref_(o){ return eval(o); };',
'function broadcast(m){ return message(m); };',
'function message(m){ self.trigger( new Event({}, { type: "message", message: m }) ); };',
'function listen(fn){ timer.listeners.push( fn ); };',
'function resolve(v){ promiseResolve(v); };',
'function reject(v){ promiseReject(v); };'
].join('\n') + fnStr;
// the .run() code
eval( fnStr ); // jshint ignore:line
},
message: function( m ){
var ls = timer.listeners;
for( var i = 0; i < ls.length; i++ ){
var fn = ls[i];
fn( m );
}
}
};
timer.exec();
}
}).then(function( v ){
_p.running = false;
_p.ran = true;
self.trigger('ran');
return v;
});
if( _p.queue == null ){
_p.queue = runP; // i.e. first step of inductive promise chain (for queue)
}
return runP;
},
// send the thread a message
message: function( m ){
var _p = this._private;
if( _p.webworker ){
_p.webworker.postMessage( m );
}
if( _p.child ){
_p.child.send( m );
}
if( _p.timer ){
_p.timer.message( m );
}
return this; // chaining
},
stop: function(){
var _p = this._private;
if( _p.webworker ){
_p.webworker.terminate();
}
if( _p.child ){
_p.child.kill();
}
if( _p.timer ){
// nothing we can do if we've run a timeout
}
_p.stopped = true;
return this.trigger('stop'); // chaining
},
stopped: function(){
return this._private.stopped;
}
});
// turns a stringified function into a (re)named function
var fnAs = function( fn, name ){
var fnStr = fn.toString();
fnStr = fnStr.replace(/function\s*?\S*?\s*?\(/, 'function ' + name + '(');
return fnStr;
};
var defineFnal = function( opts ){
opts = opts || {};
return function fnalImpl( fn, arg1 ){
var fnStr = fnAs( fn, '_$_$_' + opts.name );
this.require( fnStr );
return this.run( [
'function( data ){',
' var origResolve = resolve;',
' var res = [];',
' ',
' resolve = function( val ){',
' res.push( val );',
' };',
' ',
' var ret = data.' + opts.name + '( _$_$_' + opts.name + ( arguments.length > 1 ? ', ' + JSON.stringify(arg1) : '' ) + ' );',
' ',
' resolve = origResolve;',
' resolve( res.length > 0 ? res : ret );',
'}'
].join('\n') );
};
};
util.extend(thdfn, {
reduce: defineFnal({ name: 'reduce' }),
reduceRight: defineFnal({ name: 'reduceRight' }),
map: defineFnal({ name: 'map' })
});
// aliases
var fn = thdfn;
fn.promise = fn.run;
fn.terminate = fn.halt = fn.stop;
fn.include = fn.require;
// pull in event apis
util.extend(thdfn, {
on: define.on(),
one: define.on({ unbindSelfOnTrigger: true }),
off: define.off(),
trigger: define.trigger()
});
define.eventAliasesOn( thdfn );
module.exports = Thread;
},{"./define":1,"./event":2,"./is":5,"./promise":6,"./util":8,"./window":9,"child_process":undefined,"path":undefined}],8:[function(_dereq_,module,exports){
;
var is = _dereq_('./is');
var util;
// utility functions only for internal use
util = {
// the jquery extend() function
// NB: modified to use is etc since we can't use jquery functions
extend: function() {
var options, name, src, copy, copyIsArray, clone,
target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false;
// Handle a deep copy situation
if ( typeof target === 'boolean' ) {
deep = target;
target = arguments[1] || {};
// skip the boolean and the target
i = 2;
}
// Handle case when target is a string or something (possible in deep copy)
if ( typeof target !== 'object' && !is.fn(target) ) {
target = {};
}
// extend jQuery itself if only one argument is passed
if ( length === i ) {
target = this;
--i;
}
for ( ; i < length; i++ ) {
// Only deal with non-null/undefined values
if ( (options = arguments[ i ]) != null ) {
// Extend the base object
for ( name in options ) {
src = target[ name ];
copy = options[ name ];
// Prevent never-ending loop
if ( target === copy ) {
continue;
}
// Recurse if we're merging plain objects or arrays
if ( deep && copy && ( is.plainObject(copy) || (copyIsArray = is.array(copy)) ) ) {
if ( copyIsArray ) {
copyIsArray = false;
clone = src && is.array(src) ? src : [];
} else {
clone = src && is.plainObject(src) ? src : {};
}
// Never move original objects, clone them
target[ name ] = util.extend( deep, clone, copy );
// Don't bring in undefined values
} else if ( copy !== undefined ) {
target[ name ] = copy;
}
}
}
}
// Return the modified object
return target;
},
error: function( msg ){
if( console ){
if( console.error ){
console.error.apply( console, arguments );
} else if( console.log ){
console.log.apply( console, arguments );
} else {
throw msg;
}
} else {
throw msg;
}
}
};
module.exports = util;
},{"./is":5}],9:[function(_dereq_,module,exports){
module.exports = ( typeof window === 'undefined' ? null : window );
},{}]},{},[4])(4)
});
//# sourceMappingURL=weaver.js.map