Javascript Generators


Slides - http://bit.do/nodegenerators

In browser


ECMAScript 6 compatibility table
http://kangax.github.io/compat-table/es6/

Firefox - from v28

Chrome - from v30
(have to be enabled via "Experimental Javascript features" flag)

In nodejs (unstable)



    $ nvm install v0.11.13



    $ node -v

    0.11.13



    $ node --harmony generators.js

Basics


    function* two() {
        yield 1;
        yield 2;
    }

    var gen = two();
    console.log( gen.next() ); // { value: 1, done: false }
    console.log( gen.next() ); // { value: 2, done: false }
    console.log( gen.next() ); // { value: undefined, done: true }

    console.log( gen.next() ); // Error: Generator has already finished

Iterator


    function* odd(limit) {
        for (var i = 0; i < limit; i++) {
            if (i % 2) yield i;
        }
    }


    for (var i  of odd(10)) {
      console.log(i);
    }
    // 1 3 5 7 9

Infinite sequences


    function fibonacci(){
        var fn1 = 1;
        var fn2 = 1;
        while (1){
            var current = fn2;
            fn2 = fn1;
            fn1 = fn1 + current;
            yield current;
        }
    }


    var sequence = fibonacci();
    console.log(sequence.next()); // 1
    console.log(sequence.next()); // 1
    console.log(sequence.next()); // 2
    console.log(sequence.next()); // 3
    console.log(sequence.next()); // 5


    function* fibonacci(){
        var fn1 = 1;
        var fn2 = 1;
        while (1){
            var current = fn2;
            fn2 = fn1;
            fn1 = fn1 + current;
            var reset = yield current;
            if (reset){
                fn1 = 1;
                fn2 = 1;
            }
        }
    }


    var sequence = fibonacci();
    console.log(sequence.next());     // 1
    console.log(sequence.next());     // 1
    console.log(sequence.next());     // 2
    console.log(sequence.next());     // 3
    console.log(sequence.next(true)); // 1
    console.log(sequence.next());     // 1
    console.log(sequence.next());     // 2

Recursion


    function* factorial(n) {
        return n === 0 ? 1 : n*(yield* factorial(n-1));
    }

    var gen = factorial(5);
    console.log(gen.next()); // { value: 120, done: true }

Callback hell


    fs.readdir(source, function(err, files) {
      if (err) {
        console.log('Error finding files: ' + err)
      } else {
        files.forEach(function(filename, fileIndex) {
          gm(source + filename).size(function(err, values) {
            if (!err) {
              widths.forEach(function(width, widthIndex) {
                // ...
              }.bind(this))
            }
          })
        })
      }
    }

Read JSON


    function readJSONSync(filename) {
        return JSON.parse(fs.readFileSync(filename, 'utf8'))
    }
    

Async


    function readJSON(filename, callback) {
        fs.readFile(filename, 'utf8', function (err, res){
            if (err) return callback(err)
            callback(null, JSON.parse(res))
        })
    }

Async 2


    function readJSON(filename, callback) {
        fs.readFile(filename, 'utf8', function (err, res){
            if (err) return callback(err)
            try {
                callback(null, JSON.parse(res))
            } catch(ex) {
                callback(ex);
            }
        })
    }

Async 3


    function readJSON(filename, callback) {
        fs.readFile(filename, 'utf8', function (err, res){
            if (err) return callback(err)
            try {
                res = JSON.parse(res)
            } catch (ex) {
                return callback(ex)
            }
            callback(null, res)
        })
    }

Promise


    var Promise = Promise || require('es6-promise').Promise;

    function readFile(filename) {
        return new Promise(function(resolve, reject) {
            fs.readFile(filename, function(err, res) {
                err ? reject(err) : resolve(res);
            });
        });
    }

Async with promise


    function readJSON(filename) {
        return readFile(filename).then(function (res) {
            return JSON.parse(res)
        });
    }


    function readJSON(filename){
        return readFile(filename).then(JSON.parse);
    }

So what's wrong with Promises?


    readJSON(file1).then(function(content1) {
        // do something
        return readJSON(file2);
    }).then(function(content2) {
        // ...
    }).then(function() {
        // ...
    }).then(function() {
        // ...
    });

Sequence of operations


    db.connect(options).then(function() {
        return db.getPostById(postId);
    }).then(function(post) {
        var tags = post.tags.split(',');

        return Q.all(tags.map(function(tag) {
            return db.getPostsByTag(tag);
        })).then(function(taggedPosts) {
            db.disconnect();
        });
    });

Generators and promises together

Read JSON sync


    function readJSONSync(filename) {
        return JSON.parse(fs.readFileSync(filename, 'utf8'))
    }

Read JSON async


    var readJSON = handle(function* (filename) {
        return JSON.parse(yield readFile(filename, 'utf8'));
    });

Sequence of operations


    handle(function* () {
        yield db.connect(options);

        var post = yield db.getPostById(postId);

        var tags = post.tags.split(',');

        var taggedPosts = tags.map(function(tag) {
            return yield db.getPostsByTag(tag);
        });

        db.disconnect();
    })();

Error handling


    handle(function* () {

        try {
            var content1 = yield read(file1);
            var content2 = yield read(file2);
            var content3 = yield read(file3);
        } catch(e) {
            console.log("Error: " + e.message);
        }

    })();

How it works?


    function* read() {
        var content = yield readFile('basic.js');
        console.log(content.length);
    }

    var gen = read();

    var promise = gen.next().value;

    promise.then(gen.next.bind(gen), gen.throw.bind(gen));


    function handle(fn) {
        return function() {
            var generator = fn.apply(this, arguments);

            function next(result) {
                return result.done ? result.value : result.value.then(function (res){
                    return next(generator.next(res))
                }, function (err) {
                    return next(generator.throw(err))
                });
            }

            return next(generator.next());
        }
    }

Suspend


    var suspend = require('suspend');

    suspend(function* (resume) {
        return JSON.parse(yield fs.readFile(__filename, 'utf8', resume));
    });

CO


    function read(file) {
      return function(fn){
        fs.readFile(file, 'utf8', fn);
      }
    }


    var co = require('co');

    co(function *(){
        var a = yield read('.gitignore');
        var b = yield read('Makefile');
        var c = yield read('package.json');
        console.log(a.length);
        console.log(b.length);
        console.log(c.length);
    })();

Q.async


    var generator = Q.async(function* () {
        var ten = yield 10;
        console.log(ten, 10);
        var twenty = yield ten + 10;
        console.log(twenty, 20);
        var thirty = yield twenty + 10;
        console.log(thirty, 30);
        return thirty + 10;
    });

    generator().then(function (forty) {
        console.log(forty, 40);
    }, function (reason) {
        console.log("reason", reason);
    });

Express


    app.post('/users', Q.async(function* (req, res) {
        var user = new User(req.params);

        if (yield user.save()) {
            res.send( JSON.stringify(user) );
        } else {
            res.send(422);
        }
    }));

Koa

Next generation web framework for node.js


    var koa = require('koa');
    var app = koa();

    // x-response-time

    app.use(function *(next){
      var start = new Date;
      yield next;
      var ms = new Date - start;
      this.set('X-Response-Time', ms + 'ms');
    });

Mocha


    $ mocha --harmony test.js


    describe('Some stuff', function() {
        // ...

        it('should do async operation', Q.async(function* (done) {
            var a = yield b();
            done();
        }));
    });

Async


    function(args, function(err, res) {
        function(args2, function(err, res) {
            function(args3, function(err, res) {
                // ...
            });
        });
    });

Promises


    func().then(function(res) {
        // ...
    }).then(function(res) {
        // ...
    }).then(...);

Generators + Promises


    async(function* () {
        var res = yield func();
        // ...
    })();

We can do it better!


With async await in JavaScript


    var fn = handle(function* () {
        var content = yield readFile(filename);
        // ...
    });


    async function fn() {
        var content = await readFile(filename);
        // ...
    }

Polyfills


Google Traceur Compiler

https://github.com/google/traceur-compiler

Facebook Regenerator

http://facebook.github.io/regenerator/

Thank you!

Slides: http://bit.do/nodegenerators



M. Kachanovskyi