______ ______ ______ ______ ______________ _________ __________ /_ ___ /______ __________ /__
_ __ `/__ ___/__ / / /__ __ \_ ___/__ __ \__ / _ __ \_ ___/__ //_/
/ /_/ / _(__ ) _ /_/ / _ / / // /__ _ /_/ /_ / / /_/ // /__ _ ,< \__,_/ /____/ _\__, / /_/ /_/ \___/ /_.___/ /_/ \____/ \___/ /_/|_| /____/ ==================================================================
A fully fledged flow control library built on top of fibers. Don't want to use Fibers and on node v4+? Check out asyncblock-generators.
###Installation
npminstallasyncblockSee node-fibers for more information on fibers
- Write async code in synchronous style without blocking the event loop
- Effortlessly combine serial and parallel operations with minimal boilerplate
- Produce code which is easier to read, reason about, and modify
- Compared to flow control libraries, asyncblock makes it easy to share data between async steps. There's no need to create variables in an outer scope or use "waterfall".
- Simplify error handling practices
- If an error occurs in an async step, automatically call your callback with the error, or throw an Error
- Improve debugging by not losing stack traces across async calls
- Line numbers don't change. What's in the stack trace maps directly to your code (You may lose this with CPS transforms)
- If using a debugger, it's easy to step line-by-line through asyncblock code (compared to async libraries)
Check out the overview to get an at-a-glance overview of the different ways asyncblock can be used.
A few quick examples to show off the functionality of asyncblock:
asyncblock(function(flow){console.time('time');setTimeout(flow.add(),1000);flow.wait();//Wait for the first setTimeout to finishsetTimeout(flow.add(),2000);flow.wait();//Wait for the second setTimeout to finishconsole.timeEnd('time');//3 seconds});varab=require('asyncblock');ab(function(flow){console.time('time');setTimeout(flow.add(),1000);setTimeout(flow.add(),2000);flow.wait();//Wait for both setTimeouts to finishconsole.timeEnd('time');//2 seconds});varab=require('asyncblock');ab(function(flow){//Start two parallel file readsfs.readFile(path1,'utf8',flow.set('contents1'));fs.readFile(path2,'utf8',flow.set('contents2'));//Print the concatenation of the results when both reads are finishedconsole.log(flow.get('contents1')+flow.get('contents2'));//Wait for a large number of tasksfor(vari=0;i<100;i++){//Add each task in parallel with i as the keyfs.readFile(paths[i],'utf8',flow.add(i));}//Wait for all the tasks to finish. Results is an object of the form {key1: value1, key2: value2, ...}varresults=flow.wait();//One-liner syntax for waiting on a single taskvarcontents=flow.sync(fs.readFile(path,'utf8',flow.callback()));//See overview & API docs for more extensive description of techniques});//asyncblock.enableTransform() must be called before requiring modules using this syntax.//See overview / API for more detailsvarab=require('asyncblock');if(ab.enableTransform(module)){return;}ab(function(flow){//Start two parallel file readsvarcontents1=fs.readFile(path1,'utf8').defer();varcontents2=fs.readFile(path2,'utf8').defer();//Print the concatenation of the results when both reads are finishedconsole.log(contents1+contents2);varfiles=[];//Wait for a large number of tasksfor(vari=0;i<100;i++){//Add each task in parallel with i as the keyfiles.push(fs.readFile(paths[i],'utf8').future());}//Get an array containing the file read resultsvarresults=files.map(function(future){returnfuture.result;});//One-liner syntax for waiting on a single taskvarcontents=fs.readFile(path,'utf8').sync();//See overview & API docs for more extensive description of techniques});varab=require('asyncblock');if(ab.enableTransform(module)){return;}varasyncTask=function(callback){ab(function(flow){varcontents=fs.readFile(path,'utf8').sync();//If readFile encountered an error, it would automatically get passed to the callbackreturncontents;//Return the value you want to be passed to the callback},callback);//The callback can be specified as the 2nd arg to asyncblock. It will be called with the value returned from the asyncblock as the 2nd arg.//If an error occurs, the callback will be called with the error as the first argument.});See error handling documentation
See formatting results documentation
Both fibers, and this module, do not increase concurrency in nodejs. There is still only one thread executing at a time. Fibers are threads which are allowed to pause and resume where they left off without blocking the event loop.
- Fibers are fast, but they're not the fastest. CPU intensive tasks may prefer other solutions (you probably don't want to do CPU intensive work in node anyway...)
- Not suitable for cases where a very large number are allocated and used for an extended period of time (source)
- It requires V8 extensions, which are maintained in the node-fibers module
- In the worst case, if future versions of V8 break fibers support completely, a custom build of V8 would be required
- In the best case, V8 builds in support for coroutines directly, and asyncblock becomes based on that
- When new versions of node (V8) come out, you may have to wait longer to upgrade if the fibers code needs to be adjusted to work with it
A sample program in pure node, using the async library, and using asyncblock + fibers.
functionexample(callback){varfinishedCount=0;varfileContents=[];varcontinuation=function(){if(finishedCount<2){return;}fs.writeFile('path3',fileContents[0]+fileContents[1],function(err){if(err){thrownewError(err);}fs.readFile('path3','utf8',function(err,data){console.log(data);console.log('all done');});});};fs.readFile('path1','utf8',function(err,data){if(err){thrownewError(err);}fnishedCount++;fileContents[0]=data;continuation();});fs.readFile('path2','utf8',function(err,data){if(err){thrownewError(err);}fnishedCount++;fileContents[1]=data;continuation();});}varasync=require('async');varfileContents=[];async.series([function(callback){async.parallel([function(callback){fs.readFile('path1','utf8',callback);},function(callback){fs.readFile('path2','utf8',callback);}],function(err,results){fileContents=results;callback(err);});},function(callback){fs.writeFile('path3',fileContents[0]+fileContents[1],callback);},function(callback){fs.readFile('path3','utf8',function(err,data){console.log(data);callback(err);});}],function(err){if(err){thrownewError(err);}console.log('all done');});varab=require('asyncblock');ab(function(flow){fs.readFile('path1','utf8',flow.add('first'));fs.readFile('path2','utf8',flow.add('second'));//Wait until done reading the first and second files, then write them to another filefs.writeFile('path3',flow.wait('first')+flow.wait('second'),flow.add());flow.wait();//Wait on all outstanding tasksfs.readFile('path3','utf8',flow.add('data'));console.log(flow.wait('data'));//Print the 3rd file's dataconsole.log('all done');});//Requires asyncblock.enableTransform to be called before requiring this modulevarab=require('asyncblock');if(ab.enableTransform(module)){return;}ab(function(flow){varfirst=fs.readFile('path1','utf8').defer();varsecond=fs.readFile('path2','utf8').defer();fs.writeFile('path3',first+second).sync();varthird=fs.readFile('path3','utf8').defer();console.log(third);console.log('all done');});