awync

1.2.5 • Public • Published

Awync

Lightweight async / await module for NodeJS (and / or) Browsers

You have trouble with NodeJS's callback hell, don't you?

This tiny and flexible module will save you tons of times until the next version of Javascript (ES7) comes around.

Features

  • Single method for all configuration
  • Fluent await operations with generators
  • Simple usage.
  • It does not have any side effects.

Getting Started

There is a common problem with NodeJS called like "callback hell"

It is something like:

    const fs = require('fs');
    function updateFile(file, done){
        fs.access(file, fs.F_OK, function(err, result){
            if(err) return done(err);
    
            fs.readFile(file, 'utf8', function(err, fileData) {
                if(err) return done(err);
                
                // do something with file data then update
                
                fs.writeFile(file, 'utf8', fileData, function(err) {
                    if(err) return done(err);
                    done();
                });
            });
        });
    }
    
    updateFile(file, function(err){
        if(err){
            return console.log('Error %s', err)
        }
        console.log('saved');
    });

On ES7 (aka ES2017) this issue will be solved with async & await operations. The proposed pattern is like this:

    const fs = require('fs');
    async function updateFile(file) {
        await fs.accessAsync(file);
        var fileData = await fs.readFileAsync(file, 'utf8');
        // do something with fileData
        await fs.writeFileAsync(file, 'utf8');
    }
    
    (async function() {
        try{
            await updateFile(file);
            console.log('saved');
        }catch(err){
            console.log('Error %s', err)
        }
    })();

Easy, right ?

But, NodeJS currently does not have support for ES7 syntax. There are many other solutions focused on this issue. Most common solution is transpiling ES7 code to supported environment (currently it is ES6) Since it is not natively supported, I do not prefer that.

Then, What is my solution ?

This tiny module aiming to cover this issue by using ES6's generators. With awync the code above will be written like this:

    const awync = require('awync');
    const fs = awync()(require('fs'));
    
    function *updateFile(file) {
        yield await.fs.access(file);
        var fileData = yield fs.readFile(file, 'utf8');
        // do something with fileData
        yield fs.writeFile(file, 'utf8');
    }
    
    awync(function*(){
        try{
            yield updateFile(file);
            console.log('saved');
        }catch(err){
            console.log('Error %s', err)
        }
    });

Installation

    npm i awync --save

How It Works ?

So, NodeJS has a design pattern (Does it? Not sure! But every built-in library has the same callback function signature)

It is like:

    someLibrary.someMethod(arg1, arg2, ...argN, CALLBACK);

And the CALLBACK function signature is (mostly) :

    function(err, result) {}

Am I right ? Then, let's move!

So, Awync takes advantage of this pattern. (BTW, awync is made with AWait + asYNC! :))

  • It generates Promise for this CALLBACK method.
  • Executes that promise in a Generator Function (function*(){})
  • Pushes the result as next yield result.
  • Executes each yield iteration next after previous yield result.

How CALLBACKs will be yielded ?

When you await a CALLBACK the result will be

  • If err argument is instance of Error then generator will throw that error and will cancel current execution scope. And all of passed arguments (including err argument) will be attached to the error with name "args". so, check err.args for all arguments
  • If err argument is null or undefined then result parameter will be yielded.
    try{
        var a = await.fs.lstat(filename);
        // result is set to a
    } catch (err) {
        // err is here
        // see err.args for all arguments passed to lstat function
    }
  • If err argument does not fit the rules above then yielded value will be an array of all passed arguments
    // npm i awync-events --save
    
    const EventEmitter = require('awync-events');
    var ee = new EventEmitter();

    awync(function *(){
        var args = yield ee.when.test();
        console.log(args);
        // output will be 1, 2, 3, 4, 5
    });
    
    setTimeout(function(){
        ee.emit('test', 1,2,3,4,5);
    }, 1000);

Usage

Basic Usage

It should be simple as possible.

    const awync = require('awync');
    const fs = require('fs');
    const await = awync({fs});
    
    awync(function *() {
        var jsonString = JSON.stringify({key:'value'});

        // write the file
        yield await.fs.writeFile('./file.json', jsonString, 'utf8');

        // read the file
        var someString = yield await.fs.readFile('./file.json', 'utf8');

        // delete file
        yield await.fs.unlink('./file.json');

        assert(jsonString === someString);
        
        // next operations...
    });
    

Error handling

Only the root generator is responsible for error handling. So a simple try catch will be enough for all!

    const awync = require('awync');
    const fs = awync({someKey: require('fs')}).someKey;
    
    awync(function *() {
        var file = './someFileThat.notExist';
        try{
            var stats = yield fs.lstat(file);
        }catch (err){
            console.log(err.message);
            // File not found!
            return someFunction();
        }
        
        // Interesting! File is exist. Lets delete it!
        yield fs.unlink(file);
    });
    

Promises

    const awync = require('awync');
    
    awync(function *() {
        
        yield new Promise(resolve => {
            setTimeout(() => {
                console.log('This comment will be on 1st line. Date: %s', new Date());
                resolve();
            }, 2500);
        });


        // See the timeout delay below. It is 1500, and the above one is 2500.
        // But the below one will be executed later
        yield new Promise(resolve => {
            setTimeout(() => {
                console.log('This comment will be on 2nd line. Date: %s', new Date());
                resolve();
            }, 1500);
        });

        yield new Promise(resolve => {
            setTimeout(() => {
                console.log("This comment will be on 3rd line. Date: %s", new Date());
                resolve();
            }, 1000);
        });

        
        // next operations...
    });
    

Simple, right ?

Inner Generators

    const awync = require('awync');

    function *generatorA(name, delay = 1000){
        yield new Promise(resolve => {
            setTimeout(() => {
                console.log('Hello %s on %s', name, new Date());
                resolve();
            }, delay);
        });
    }

    function *generatorB(count = 1){
        yield generatorA('Friend ' + count);
        yield generatorA('World ' + count);
    }


    function *generatorC(){
        for(var i=0; i<5; i++){
            yield generatorB(i);
        }
    }

    awync(function *() {

        yield generatorC();

        // next operations...
    });
    

Iterables, Arrays and Callbacks

Please notice the usage of awync.iterator and awync.callback

    const awync = require('awync');

    function asyncLog(message, startDate, delay, next) {
        delay = Math.floor(delay);
        setTimeout(function () {
            console.log(message + '[Completed after %sms (Delay was %sms)]', Date.now() - startDate, delay);
            next();
        }, delay);
    }
    
    var array = [];
    var i;
    for(i=0; i<20; i++){
        array.push(asyncLog);
    }
    var set = new Set();
    for(i=0; i<20; i++){
        // Set accepts unique values, 
        // And function.bind generates new reference
        set.add(asyncLog.bind());
    }
    
    awync(function *() {
        var date = Date.now();
    
        for(let callback of awync(awync.iterator, array)()){
            yield callback('Message from Array', date, 100);
        }
    
        console.log('\n------------- ARRAY ITERATOR COMPLETED ------------------\n');
    
        for(let callback of awync(set)()){
            yield callback('Message from Set', date, 150);
        }
    
        console.log('\n------------- SET ITERATOR COMPLETED ------------------\n');
    
        yield awync(awync.callback, asyncLog)('Message from Callback', date, 200);
    
        console.log('\n------------- CALLBACK COMPLETED ------------------\n');
    
        console.log("Done! It should completed within %sms but it completed within %sms", (array.length * 100) + (set.size * 150) + 200, Date.now() - date);
    });
    

Parallel Execution

    const awync = require('awync');

    var executors = [
        function *() {
            yield awync(awync.callback, function (next) {
                setTimeout(function () {
                    console.log("Executor 1");
                    next();
                }, 1000);
            })();
        },
        function *() {
            yield awync(awync.callback, function (next) {
                setTimeout(function () {
                    console.log("Executor 2");
                    next();
                }, 500);
            })();
        },
        function *() {
            yield awync(awync.callback, function (next) {
                setTimeout(function () {
                    console.log("Executor 3");
                    next();
                }, 700);
            })();
        }
    ];
    
    awync(executors);
    
    // Output will be in this order: Executor 2, Executor 3, Executor 1
    

Change Log

  • 1.1.2
    • Better promise handling
  • 1.1.1
    • Bug fix: Awaiter should only wrap non generator functions
  • 1.1.0
    • Added Iterator and Callback support
    • Added support for chained references yield await.someObject.someReference.someOtherReference.someAsyncCallbac() now works!
  • 1.0.0
    • Initial release

Any issues or new ideas ?

Fork on github: https://github.com/tlghn/awync.git

Releated Links

Package Sidebar

Install

npm i awync

Weekly Downloads

4

Version

1.2.5

License

MIT

Last publish

Collaborators

  • tolgahan