How does Generator.next() processes its parameter? - javascript

The documentation says that "You can also provide a parameter to the next method to send a value to the generator." Where does it sends it to?
For example, take these 3 generators:
function* one() {
while(true) {
var value = yield null;
}
}
var g1 = one(); g1.next();
g1.next(1000); //yields null
function* two() {
var i = 0;
while (true) {
i += yield i;
}
}
var g2 = two(); g2.next();
g2.next(1000) // yields 1000
function* three(){
var index = 0;
while (true)
yield index++;
}
g3 = three();
g3.next();
g3.next(1000); // yields 1
In generators 3 and 1, the argument passed has no effect on next. Why is that? How does generator 2 calculates its return value? Why it is affected by the given argument?

The key to understanding this is knowing how the next function retrieves the argument passed to next(), which is as the return value of the yield operator:
[rv] = yield [expression];
Independently of the value of [expression], yield will assign to rv the value passed to next().
But, here comes the tricky part: yield will only assign the value passed to next() when resuming execution from a previous iteration. As a consequence, on the first iteration, yield does not assign anything to rv.
For example, if I have this generator:
function* gen() {
// On the first iteration, yield does not return anything.
//because it returns something ONLY when execution is resumed
returnedFromYield = yield 'foo';
yield returnedFromYield;
}
returnedFromYield is undefined on the first iteration. When execution is resumed on the second iteration, yield assigns the passed value to the returnedFromYield variable, which is then returned:
g.next(1); // 'foo'
g.next(2); // 2
Let's review another example:
function* gen() {
yield yield yield 5;
}
On the first iteration, (g.next()), yield will return 5, on the second iteration, (g.next(10)) yield is going to pass 10 to the second yield. That is, yield yield yield 5; on the second iteration is equivalent to yield yield 10;, and, on the third iteration, it's equivalent to yield valuePassedToNext.

Here's an annotated example showing the logical flow and scope for the values passed to a generator's next method. The affected area of the next method call is highlighted in the corresponding color.
a: There is no yield, so argument 'one' is ignored. The generator pauses on line 5, returning the value 1.
b: Execution resumes and 'two' replaces yield on line 5, and is assigned to the yieldValue variable. Line 6 is executed. The generator pauses on line 8, returning the value 2.
c: Execution resumes and 'three' replaces yield on line 8, and is assigned to the yieldValue variable. Line 9 is executed. The generator pauses on line 11, returning the value 3.
d: Execution resumes and 'four' replaces yield on line 11, and is assigned to the yieldValue variable. Line 12 is executed. Line 14 is executed. Finally the generator is now done, returning the value undefined.
See the logs on line 28-36

Related

Why dosent fruit get assigned 'apple' in this generator function? [duplicate]

This question already has answers here:
console.log not being called from generator function
(1 answer)
Understanding generators in JavaScript
(3 answers)
Where argument of first next() call goes? [duplicate]
(2 answers)
ES6 yield : what happens to the arguments of the first call next()?
(1 answer)
In ES6, what happens to the arguments in the first call to an iterator's `next` method?
(3 answers)
Closed 1 year ago.
Title says it all.
Output is:
starting
lol
undefined
0 undefined
Expected:
starting // yield giving us back a string
lol // yield giving back result from bar()
'apples' // since the 'yield bar()' is 'replaced' by 'apples' via next('apples')
'0 apples' // the last yield should tell us what fruit is, but its undefined
Code:
const bar = () => 'lol'
function* foo1() {
yield 'starting'
let a = 0
let fruit = yield bar()
console.log(fruit) // <-- y no apples? I could even see 'lol' being here too.
yield `${a} ${fruit}`
}
let g = foo1()
console.log(g.next().value)
console.log(g.next('apples').value)
console.log(g.next().value)
According to Mozilla Docs function*
// the first call of next executes from the start of the function until the first yield statement
You must 'init' with a first next() call before getting yield statement
const bar = () => 'lol'
function* foo1() {
yield 'starting'
let a = 0
let fruit = yield bar()
console.log(fruit) // <-- y no apples? I could even see 'lol' being here too.
yield `${a} ${fruit}`
}
let g = foo1()
g.next();
console.log( g.next().value)
console.log( g.next('apples').value )

next() behaves differently directly on generator vs variable with generator value

Why is it that I get different results when calling next() directly on a generator, versus on a variable with the same generator assigned as its value?
All code/output below.
Below is the generator, plus variable declaration/assignment:
function* gen() {
yield 1;
yield 2;
yield 3;
};
let genVar = gen();
First code snippet:
let first = genVar.next();
console.log(first);
second = genVar.next();
console.log(second);
Output of first code snippet:
{ value: 1, done: false }
{ value: 2, done: false }
Second code snippet:
let one = gen().next();
console.log(one);
two = gen().next();
console.log(two);
Output of second code snippet:
{ value: 1, done: false }
{ value: 1, done: false }
My best guess at the moment is this has something to do with assignment by value/reference?
Anytime you execute the generator function, you create a brand new generator object that allows to iterate over the steps of the generator function from the start.
So in your second example, you actually create two different iterators.

How exactly does this delegating recursion example from YDKJS work?

I started reading You Don't Know JS: Async and Performance and tripped at delegating recursion example: I went through the code mentally and got the right result, but can't comprehend the description of the intermediate steps in the book.
Tried inserting console.log() into the functions' body, tried debugger to examine the call stack and still cannot conform my mental model of the code to the one, that's in the book.
function run(), which gets generator function as parameter, creates its instance and run it to the end, passing each previously yielded value to the next() call.
function run(gen) {
var args = [].slice.call( arguments, 1), it;
// initialize the generator in the current context
it = gen.apply( this, args );
// return a promise for the generator completing
return Promise.resolve()
.then( function handleNext(value){
// run to the next yielded value
var next = it.next( value );
return (function handleResult(next){
// generator has completed running?
if (next.done) {
return next.value;
}
// otherwise keep going
else {
return Promise.resolve( next.value )
.then(
// resume the async loop on
// success, sending the resolved
// value back into the generator
handleNext,
// if `value` is a rejected
// promise, propagate error back
// into the generator for its own
// error handling
function handleErr(err) {
return Promise.resolve(
it.throw( err )
)
.then( handleResult );
}
);
}
})(next);
} );
}
the example code:
function *foo(val) {
if (val > 1) {
// generator recursion
val = yield *foo( val - 1 );
}
return yield request( "http://some.url/?v=" + val );
}
function *bar() {
var r1 = yield *foo( 3 );
console.log( r1 );
}
run( bar );
and for convenience's sake we can implement function request() like this:
function request(url) {
return new Promise(function(resolve){
setTimeout(function(){
resolve( url.match(/v=(\d+)$/)[1] );
},1000);
});
}
The book provides these steps:
run(bar) starts up the *bar() generator.
foo(3) creates an iterator for *foo(..) and passes 3 as its val parameter.
Because 3 > 1, foo(2) creates another iterator and passes in 2 as its val parameter.
Because 2 > 1, foo(1) creates yet another iterator and passes in 1 as its val parameter.
1 > 1 is false, so we next call request(..) with the 1 value, and get a promise back for that first Ajax call.
That promise is yielded out, which comes back to the *foo(2)
generator instance.
The yield * passes that promise back out to the *foo(3) generator
instance. Another yield * passes the promise out to the *bar()
generator instance. And yet again another yield * passes the promise
out to the run(..) utility, which will wait on that promise (for the
first Ajax request) to proceed.
When the promise resolves, its fulfillment message is sent to resume
*bar(), which passes through the yield * into the *foo(3) instance, which then passes through the yield * to the *foo(2) generator
instance, which then passes through the yield * to the normal yield
that's waiting in the *foo(3) generator instance.
That first call's Ajax response is now immediately returned from the
*foo(3) generator instance, which sends that value back as the result of the yield * expression in the *foo(2) instance, and
assigned to its local val variable.
Inside *foo(2), a second Ajax request is made with request(..),
whose promise is yielded back to the *foo(1) instance, and then
yield * propagates all the way out to run(..) (step 7 again). When
the promise resolves, the second Ajax response propagates all the
way back into the *foo(2) generator instance, and is assigned to
its local val variable.
Finally, the third Ajax request is made with request(..), its
promise goes out to run(..), and then its resolution value comes
all the way back, which is then returned so that it comes back to
the waiting yield * expression in *bar().
Everything is clear until the 8th step.
...which then passes through the yield * to the normal yield
that's waiting in the *foo(3) generator instance.
Why waiting in foo(3), not in foo(2)? I thought after Promise fulfillment, its value (1) is passed to return yield request( "http://some.url/?v=" + val ); line, in place of yield, so we have return 1 at the end of foo(1). And then 1 is passed to val = yield *foo( val - 1 ); line, again, in place of yield, so we have val = 1 inside foo(2) call. After that, a second request() is made and yields a Promise to foo(3).
Then foo(3) yields the Promise to bar(), then bar() yields the Promise to run(). run() waits on the second Promise, just as with the first promise, and so on.
JSFiddle
What have I overlooked?
What have I overlooked?
Nothing. In steps 8 and 9, the generator they should be referring to is the one created by foo(1), not by foo(3).

Progressive (or iterative) Callbacks

I read the other day about a special type of callback that progresses with you as you call it repeatedly. As in it does A the first time it's called and returns, then it does B the next time it's called. Eventually it signals in some way that the final action has been taken.
I can't remember what it was called and I can't find it in my history so I don't know what to search. I need help.
I think what you might be looking for is generator functions. It has been introduced by ES6.
Calling a generator function does not execute its body immediately; an iterator object for the function is returned instead. When the iterator's next() method is called, the generator function's body is executed until the first yield expression, which specifies the value to be returned from the iterator
See MDN documentation.
Example:
function* idMaker() {
var index = 0;
while (index < 3)
yield index++;
}
var gen = idMaker();
console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // undefined
You may be talking about generators:
> function* x() { yield 1; yield 2; yield 3; return 4; }
undefined
> var g = x();
undefined
> g.next();
{ value: 1, done: false }
> g.next();
{ value: 2, done: false }
> g.next();
{ value: 3, done: false }
> g.next();
{ value: 4, done: true }
> g.next();
{ value: undefined, done: true }
See:
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/function%2A
https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Iterators_and_Generators
Generators can be used for asynchronous operations when a generator yields promises instead of values that it wants to return that it itself expects to get injected as a resolved values of those promises in the return value of the yield statement by the wrapper like that from co or Bluebird.coroutine - see:
https://www.npmjs.com/package/co
http://bluebirdjs.com/docs/api/promise.coroutine.html
This was the basis of the new async and await keywords in newer versions of JavaScript:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

Javascript strange generator yield sub function behavior

I'm using MySQL (mysql-co) and ASQ(asynquence) in a simple project to get a better understanding of ES6 generators and yield functions, and I'm stumped on an odd behavior.
Short explanation of asynquence
asynquence (https://github.com/getify/asynquence) provides an easy way for me to run generators in sequence. It can also do pseudo-parallel execution but that's not what I need for now. The structure of function *x(token) is from there. token holds a connection object at [0]. yield token passes control on to the next generator function in sequence.
Code Sample 1 (works)
function *test1(token) {
var conn = token.messages[0];
var values = {id:1, dev:1, description:'This is it!'};
yield conn.query("INSERT INTO version SET ?", values);
yield token;
}
This works fine. The row described above gets inserted. I didn't know the MySQL driver allowed such a simple looking insert function but it does.
Code Sample 2 (doesn't work)
function *test1(token) {
var conn = token.messages[0];
var values = {id:1, dev:1, description:'This is it!'};
yield subtest1(conn, values);
yield token;
}
function *subtest1(conn, values) {
yield conn.query("INSERT INTO version SET ?", values);
}
This doesn't work. The actual code in question for subtest1 is in a model class, so I would prefer not to have it merged in with the controller.
I've tried a bunch of different things around with or without yield on the subtest function.
What's going on?
subtest1(conn, values) is a generator. yielding a generator object does not execute its body. That is, the yielded generator remains suspended, and it would require a call to the next() method for the first yield to be reached. There are no explicit or implicit calls to next() in Code Sample 2, and this is the reason conn.query(...) isn't executed.
How about yield* subtest1(conn, values)? From the linked page:
The yield* expression iterates over the operand and yields each value
returned by it.
It will still execute subtest lazily.
An alternative solution is to turn subtest into a regular function and return the result of conn.query(...) (assuming you only need to perform one query):
function subtest1(conn, values) {
return conn.query("INSERT INTO version SET ?", values);
}
yield subtest1(conn, values) calls subtest1(conn, values), which returns an iterator object, and then yields that from test1 as the value for that iteration of test1. It doesn't iterate the values returned by the subtest1 iterator.
You could have test1 iterate the values from subtest1's iterator by adding a * after the yield:
yield* subtest1(conn, values);
but looking at your code, I don't think you want to. If you want to split the conn.query line out into a function, it would look like this:
function *test1(token) {
var conn = token.messages[0];
var values = {id:1, dev:1, description:'This is it!'};
yield subtest1(conn, values);
yield token;
}
function subtest1(conn, values) {
return conn.query("INSERT INTO version SET ?", values);
}
This simpler version may help with the difference between yield and yield*:
function* foo() {
console.log("f");
yield bar();
console.log("f done");
}
function* bar() {
console.log("b1");
let i = 0;
while (i < 3) {
console.log("b2");
yield i;
++i;
}
console.log("b done");
}
for (let v of foo()) {
console.log(v);
}
The output of that is: (live copy on Babel's REPL)
f
{}
f done
Note how we don't see any of the "b" logging at all; that's because the {} you see after f1 is the iterator object returned by calling bar.
If we just add * on the yield bar(); line, turning it into yield* bar(), the output changes: (live copy)
f
b1
b2
0
b2
1
b2
2
b done
f done
If the operand to yield* is an iterator, yield* iterates it, yielding each of its values. It's basically:
for (value of bar()) {
yield value;
}

Categories

Resources