nicegen
Version:
nice function support for LiteScript. see: github.com/luciotato/LiteScript
206 lines (169 loc) • 7.53 kB
JavaScript
/* nicegen - based on ES6-harmony generators
- Support Lib for LiteScript
- Copyright 2014 Lucio Tato
-- WARNING: bleeding edge --
This lib use ECMAScript 6 generators,
the next version of the javascript standard, code-named "Harmony".
(release target date December 2013).
This lib also uses bleeding edge v8 Harmony features, so you’ll need to
use a (today) -unstable- nodejs version >= v0.11.6
and also pass the --harmony flag when executing node.
node --harmony test/test
*/
//if (Proxy===undefined) throw new Error('This module requires "node --harmony" & node >=0.11.9');
function nicegen(thisValue, generatorFn, args){ // wait.launchFiber(fn,arg1,arg2...)
//console.log(Array.prototype.slice.call(arguments));
// starts a generator (emulating a fiber)
//console.log('nicegen',arguments);
// checks
if (typeof generatorFn !== 'function') {
//console.log(arguments);
throw new Error('second argument must be a generator, not '+(typeof generatorFn));
}
var lastArg = args.length-1;
var finalCallback = args[lastArg];
if (typeof finalCallback !== 'function') {
//if no callback, use default
finalCallback = function(err,data){if(err) throw(err)};
lastArg++;
}
// if callback; remove final callback from arguments. The generator does not receive the callback
var generatorArgs = Array.prototype.slice.call(args,0,lastArg)
// create the iterator
var thisIterator = generatorFn.apply(thisValue, generatorArgs); // create the iterator.
// Note: calling a generator function, DOES NOT execute it.
// just creates an iterator
// create a function with this closure to act as callback for
// the async calls in this iterator
// this callback will RESUME the generator, returning 'data'
// as the result of the 'yield' keyword
thisIterator.defaultCallback=function(err,data){
// thisIterator.defaultCallback
// console.log('on callback, err:',err,'data:',data);
var nextPart = undefined;
// if err, throw inside the generator
if (err) {
try
{
//give the generator a chance to catch
nextPart = thisIterator.throw(err);
if (nextPart.done){ //iterator returned (instead of a new yield)
if (nextPart.value!==undefined) { //returned some data
//we asume error recovery
finalCallback && finalCallback(null, nextPart.value); //final value (data)
}
else {
finalCallback && finalCallback(err); //bubble up err
}
return; //nothing more to do
}
}
catch(err){
finalCallback && finalCallback(err)
return;
};
};
if (nextPart===undefined) {//if it is not a error-recovery
// data:
// RESUME the generator returning data as the result of the yield keyword,
// (we store the result of resuming the generator in 'nextPart')
try {
nextPart = thisIterator.next(data); // thisIterator.next => RESUME generator, data:->result of 'yield'
}
catch(err){
finalCallback && finalCallback(err)
return;
};
};
//after the next part of the generator runs...
if (nextPart.done) {//the generator function has finished (executed "return")
finalCallback && finalCallback(null, nextPart.value); //final value (data)
return; //it was the last part, nothing more to do
}
// else...
// not finished yet. The generator paused
// with another : yield [asyncFn,arg1,arg2...]
// so in nextPart.value we have the asyncFn to call
// Let's call the async
callTheAsync(nextPart.value, thisIterator.defaultCallback);
// the async function callback will be handled by this same closure (thisIterator.defaultCallback)
// repeating the loop until nextPart.done
};
// by calling thisIterator.defaultCallback() now, we
// RUN the first part of generator (up to 1st yield)
thisIterator.defaultCallback();
return thisIterator; //nicegen returns created iterator
}
// helper function, call the async, using theCallback as callback
function callTheAsync(yieldArr,theCallback){
var first= yieldArr[0];
if (first==='map'){
//parallel map: single param asyncFn
//yield ['map', addresses, dns, 'reverse']
parallelMap(yieldArr[1],yieldArr[2],yieldArr[3],theCallback);
}
else {
//single async call - multiple params
// get asyncFn to call
var asyncFn;
// options are; [null,function,...] or [object,'methodName',...]
if (first===null) asyncFn=yieldArr[1]
else asyncFn = yieldArr[0][yieldArr[1]];
//check: asyncFn should be a function
if (typeof asyncFn !=='function') {
throw(new Error(asyncFn.toString()+' is not a function\nyield '
+JSON.stringify(yieldArr)));
}
// prepare arguments:
// remove thisValue & fn from args and
var args = yieldArr.slice(2);
//add callback to arguments
args.push(theCallback)
// start the asyncFn
try{
asyncFn.apply(yieldArr[0], args); // call the asyncFn
}catch(err){
//a nice async function should not throw errors on call, how bad.
theCallback(err);
}
}
}
//helper function parallelMap
function parallelMap(arr, asyncFnThis, asyncFnName, finalCallback){
//
var mapResult={arr:[],count:0,expected:arr.length};
//early exit on empty array
if (mapResult.expected===0) return finalCallback(null,mapResult.arr);
var asyncFn=asyncFnThis[asyncFnName];
function callAsyncOn(inx){
asyncFn.call(asyncFnThis,arr[inx],function(err,data){
if (err) return finalCallback(err,data); //err
//console.log('result arrived',inx,data);
//store result
mapResult.arr[inx]=data;
mapResult.count++;
if (mapResult.count>=mapResult.expected) { // all results arrived
finalCallback(null,mapResult.arr) ; // final callback OK
}
});
};
//main parallel call async
for (var i = 0; i < mapResult.expected; i++) {
callAsyncOn(i);
}
}
//helper function parallelFilter (uses parallelMap)
function parallelFilter(arr, asyncFnThis, asyncFnName, finalCallback){
parallelMap( arr, asyncFnThis, asyncFnName
,function(err,testResults){ //when all the filters finish
if (err) return finalCallback(err);
// create an array with each item
// where filterGeneratorFn returned true
var filteredArr=[];
for (var i = 0; i < arr.length; i++) {
if (testResults[i]) filteredArr.push(arr[i]);
};
finalCallback(null,filteredArr);
});
}
module.exports = nicegen;