Just a learner
Just a learner

Reputation: 28562

Understanding of Javascript's generator

I have a piece of code:

function * input(){
    let array = [];
    while(true) {
        array.push(yield array);
    }
}

var gen = input();
console.log(gen.next("A"))
console.log(gen.next("B"))
console.log(gen.next("C"))
console.log(gen.next("D"))

When you run it you will get the following output:

{ value: [], done: false }
{ value: [ 'B' ], done: false }
{ value: [ 'B', 'C' ], done: false }
{ value: [ 'B', 'C', 'D' ], done: false }

Why the first line of the result doesn't include A in the array? There is one explanation from this page at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*#Passing_arguments_into_Generators. The comment says

the first call of next() executes from the start of the function until the first yield statement

But from my testing it seems not correct. My testing code is:

function* logGenerator() {
    console.log("before yield in function");
    yield 1;
    console.log("filler 1");
    yield 2;
    console.log("filler 2");
    yield 3;
    console.log("filler 3");
}

var gen = logGenerator();

console.log(gen.next());
console.log("-----------------");
console.log(gen.next());
console.log("-----------------");
console.log(gen.next());
console.log("-----------------");
console.log(gen.next());

The result is:

before yield in function
{ value: 1, done: false }
-----------------
filler 1
{ value: 2, done: false }
-----------------
filler 2
{ value: 3, done: false }
-----------------
filler 3
{ value: undefined, done: true }

As you can see, the first next() not only executed the statements before the first yield, but also the first yield statement. So that theory can't explain my question. Can anyone help to point me the right direction? Thanks in advance.

Upvotes: 3

Views: 118

Answers (2)

GantTheWanderer
GantTheWanderer

Reputation: 1292

Consider the first generator you wrote rewritten this way.

function * input(){
    let array = [];
    while(true) {
        var thingToAdd = yield array;
        console.log(thingToAdd);
        array.push(thingToAdd);
    }
}

var gen = input();
console.log(gen.next("A"))
console.log(gen.next("B"))
console.log(gen.next("C"))
console.log(gen.next("D"))

Isn't it clearer to see why "A" never gets added to the array? The first execution of the generator stops at the first yield statement well before the array ever gets modified. By the time execution returns to the generator, the passed in value is "B". The same dynamic is occurring in your code here array.push(yield array); Inner expressions are evaluated first, thus the yield halts execution before the push is accessed.

I believe that if you want to the generator to respect the first value you pass in, you need to call .next() once without any parameters. Every example I've seen does this.

Also reading the "Send" section of this article illustrates your situation.

Note that this model works well for a question and answer type interaction since we cannot have an answer before a question is asked, and all subsequent next calls will pass in the answer to the previous question and retrieve the next one.

var q1 = gen.next();
console.log(q1);
var a = userInput();
var q2 = gen.next(a);
console.log(q2);
var a2 = userInput();
...

Upvotes: 1

James Sumners
James Sumners

Reputation: 14777

function * foo () {
  var i = 0
  yield i

  i += 1
  yield i

  i += 1
  i += 2
  yield i
}

var gen = foo()
console.log(gen.next()) // 0
console.log(gen.next()) // 1
console.log(gen.next()) // 4

Notice that the var gen = foo() merely creates an instance of a generator. It's the first invocation of .next() that starts the execution of the generator. Generators execute until they reach a yield statement and return the value of that yield statement. At this point the generator is paused until another invocation of .next() is performed.

So, everything is working as intended in your examples. In the first example, the first yield statement is returning the empty array. On the next .next() the array is populated with the value passed in, and then that array is yielded. In code:

function * foo (param) {
  var array = []
  // do nothing with param
  yield array

  array.push(param) // 'B'
  yield array

  array.push(param) // 'C'
  yield array

  array.push(param) // 'D'
  yield array
}

This matches the documentation:

If an optional value is passed to the generator's next() method, that value becomes the value returned by the generator's current yield operation.

Upvotes: 0

Related Questions