UNPKG

nicegen

Version:

nice function support for LiteScript. see: github.com/luciotato/LiteScript

206 lines (169 loc) 7.53 kB
/* 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;