简体   繁体   中英

How can we use generators to write async code in a sync way?

I have read that generators, coming with ECMAScript 6 and already available in node.js dev version, will make easier to write async code in a sync way. But it was really hard to understand to me, how can we use generators to write async code ?

We first have to remember that with ES generators, we can pass a value to the next() method, which will be the return value of the yield statement in the generator.

The idea is to give the generator to a kind of controller function.

In the generator, each time we call an async function, we yield , so we give back control to the controller function. The controller function does nothing but calling next() when the async operation is done. During this time, we can handle other events, so it's non-blocking.

Example without generators :

// chain of callbacks
function findAuthorOfArticleOfComment (commentID, callback) {
  database.comments.find( {id: commentID}
                        , function (err, comment) {
    if (err) return callback(err);
    database.articles.find( { id: comment.articleID }
                          , function (err, article) {
      if (err) return callback(err);
      database.users.find( { id: article.authorID }
                         , function (err, author) {
        if (err) return callback(err);
        callback(author);
      });
    });
  });
}
findAuthorOfArticleOfComment (commentID, function(err, author) {
  if(!err) console.log(author);
}

Example with generators :

We have to use a library which will control the flow, like suspend or bluebird if you want to use it with Promises. I will give an example without library for better understanding.

function* myGenerator(resume, commentID, callback) {
  var comment, article, author;
  comment = yield database.comments.find( {id: commentID}, resume);
  article = yield database.articles.find( {id: comment.articleID}, resume);
  author = yield database.users.find( {id: article.authorID}, resume);
};

// in real life we use a library for this !
var findAuthorOfArticleOfComment = function(commentID, callback) {
  var resume, theGenerator;
  resume = function (err, result) {
    var next;
    if(err) return callback(err);
    next = theGenerator.next(result);
    if (next.done) callback(null, result);
  }
  theGenerator = myGenerator(resume, commentID, callback);
  theGenerator.next();
}

// still the same function as first example !
findAuthorOfArticleOfComment (commentID, function(err, author) {
  if(!err) console.log(author);
}

What we do :

  • create the generator, giving a resume function as the first parameter, an parameters given by the function caller
  • call next() a first time. Our first async function is reached and the generator yield.
  • each time the resume function is called, we get the value and pass it to next, so the code in the generator continue execution and the yield statement returns the correct value.

In real life we will use a library, and give the generator as parameter to a generic controller function. So all we have to write is the generator, and the value = yield asyncFunction(parameters, resumeCallback); , or value = yield functionReturningPromise(parameters); if you use Promises (with a Promise compatible library). It is really a good way to write async code in a sync looking way.


Excellent sources :
http://tobyho.com/2013/06/16/what-are-generators/
http://jlongster.com/A-Closer-Look-at-Generators-Without-Promises

Complementing Guilro's answer, generators allow you to write things like this:

controllerFunction(function*() {
    yield functionThatReturnsAThenable()
    yield anotherFunctionThatReturnsAThenable()
    return;
});

What the controller function does is call the generator, get whatever it returns from yield, chain a next() call to the then() of the returned value until the generator is done .

Something like this:

function controllerFunction(generator) {

    function run(runnableGenerator) {
        var result = runnableGenerator.next(); // or send(params)
        if (!result.done) {
            if (result.value.then) {
                result.value.then(function() {
                    run(runnableGenerator);
                });
            } else {
                run(runnableGenerator);
            }
        }
    }

    var runnableGenerator = generator();
    run(runnableGenerator);

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM