3

I've been experimenting with ES6 generators in Node now for a little while, and there's still one issue that I don't understand.

In regular callback Node, getting a value from a database and doing something else in parallel would look something like this:

function executeBoth(){

  db.find("value", function(results){
    console.log(results);
  });

  doSomethingElse("else", function(data){
    console.log(data);
  });
}

This example is totally contrived, but notice that by calling executeBoth(), db.find and doSomethingElse do not wait for each other to finish and Node can just execute both around the same time and the code is non-blocking.

Here is a generator example that would attempt to do the same thing:

function* executeBoth(){

  var results = yield db.find("value");
  console.log(results);

  var data = yield doSomethingElse("else");
  console.log(data);
}

I don't understand how the above code avoids the first function from blocking the second. From what I've read (sources below), it seems that the entire generator suspends when it reaches the yield keyword. This makes sense for the lines of code that rely on the value returned from that specific yield, but doesn't this also mean that db.find would block doSomethingElse from executing?

It seems like it can be solved by wrapping each yielded value and the following code that relies on them in their own separate generators, then calling those generators from a normal function. Yet if this the most efficient way to create non-blocking code, it would encourage the over-usage of many small generator functions with potentially repeating, specialized code. Am I correctly understanding the underlying mechanics of generators? If so, what is the workaround for this? Thanks in advance.

source one, source two, source three

user3181113
  • 758
  • 2
  • 11
  • 23
  • You might want to have a look at [Understanding code flow with yield/generators](http://stackoverflow.com/q/23551418/1048572) – Bergi Jul 01 '14 at 11:39

1 Answers1

2

You're right, in your example, the db.find method is executed, the generator waits until that action is complete and then it'll resume until it hits doSomethingElse.

I'm going to steal the following examples from the generator library co. (You don't need to use it, you can use plain generators, but I like the syntax and the examples made them 'click' for me)

Let's say that in the following examples, fetching Google takes 10 seconds, fetching Yahoo takes 6 and cloudup 5.

co(function *(){
  var a = yield get('http://google.com');
  var b = yield get('http://yahoo.com');
  var c = yield get('http://cloudup.com');
  console.log(a[0].statusCode);
  console.log(b[0].statusCode);
  console.log(c[0].statusCode);
})()

This fetches the first site, waits, then fetches the second, waits, then fetches the third. It's like your example. It'll finish in 10+6+5 = ~21 seconds.

co(function *(){
  var a = get('http://google.com');
  var b = get('http://yahoo.com');
  var c = get('http://cloudup.com');
  var res = yield [a, b, c];
  console.log(res);
})()

This, however, does something different: It starts fetching the first, second and third. The return values (a,b,c) are either promises that will eventually report that they're finished or simple callbacks.
The yield statement will wait until all three promises/callbacks are resolved. It doesn't matter in what order. It'll finish in ~10 seconds.

RickN
  • 12,537
  • 4
  • 24
  • 28
  • Yes, yielding an array of promises or thunks is more efficient, but it is still blocking. What if the get request to cloudup.com is done 4 seconds before the request to yahoo.com is done? Then the cloudup.com request has to wait to log until after the yahoo.com request because that yield suspends all execution until all three values have arrived. – user3181113 Jul 01 '14 at 19:52
  • @user3181113: Yes, that's the plan? Of course, you could (at least with promises) also do `var a = get('http://google.com'), b = get('http://yahoo.com'), c = get('http://cloudup.com'); console.log(yield a); console.log(yield b); console.log(yield c);` so that `a` can be logged if it arrives before the other two. – Bergi Jul 01 '14 at 20:33
  • good explanation, thank you – mrcrgl Aug 04 '15 at 17:40