es6 Generator while(true) - javascript

start to learning generator, i am encounter the following script.
I am confused by first next(), why the console.log is not printed for the very first next().
function* callee() {
console.log('callee: ' + (yield));
}
function* caller() {
while (true) {
yield* callee();
}
}
> let callerObj = caller();
> callerObj.next() // start
{ value: undefined, done: false }
// why console.log is not returning 'callee' ??
> callerObj.next('a')
callee: a
{ value: undefined, done: false }
> callerObj.next('b')
callee: b
{ value: undefined, done: false }

When you have a generator it starts in a suspended phase and the first next call runs the generator up to the first yield point. When you "yield from" a sub-generator, each time. If you add logging to the entry points of both your functions you will see this:
function* callee() {
console.log('Callee is running up to the first yield point');
console.log('callee: ' + (yield));
}
function* caller() {
console.log('Caller is running up to the first yield point');
while (true) {
yield* callee();
}
}
When you run this implementation using your test code you will see:
> let t = caller()
> t.next()
Caller is running up to the first yield point
Callee is running up to the first yield point
Object {value: undefined, done: false}

When starting the generator (callerObj.next()), it always goes up to the first yield.
Since you are delegating (yield *) to another generator, that yield would be the one in callee:
function* callee() {
console.log('callee: ' + (yield));
// this yield ^^^^^
}
Here the generator stops before executing console.log. If you were to yield a value back, this would be the return value of your first callerObj.next() call:
function* callee() {
console.log('callee: ' + (yield 'value'));
}
The next callerObj.next('a') call will then replace the value at yield with 'a' and call console.log. In pseudo-code:
console.log('callee: ' + 'a');
// `yield` is replaced with 'a' for this invocation
It will then run until it encounters the same yield again (since you are in an infinite loop).

Related

Using Iterable Protocol for JavaScript generator function does not reach return statement

I'm trying to learn about generator functions in javascript and am experimenting with using a for..of loop in order to iterate over my yield statements. One thing I noticed is that the function terminates after the final yield statement and does not attempt to execute the return statement. If I were to create a generator object and call generator.next() 4 times, the return statement would trigger and we would see the associated generator object. Would anyone have any idea what's going on under the hood to cause this behavior?
Thanks!
function* generator(){
yield 1
yield 2
yield 3
return "hello there"
}
for (const value of generator()){
console.log('value', value)
}
the function terminates after the final yield statement and does not attempt to execute the return statement
This is wrong, the return statement is executed, which is easy to see with
function* generator() {
yield 1
yield 2
yield 3
console.log('returning')
return "hello there"
}
for (const value of generator()) {
console.log('value', value)
}
And indeed the next method is called 4 times by the loop:
function* generator() {
yield 1
yield 2
yield 3
return "hello there"
}
const iterator = {
state: generator(),
[Symbol.iterator]() { return this; },
next(...args) {
const res = this.state.next(...args)
console.log(res)
return res
},
}
for (const value of iterator) {
console.log('value', value)
}
However, the return value, not being a yielded value, marks the end of the iteration and does not get to become a value in the for loop body, which only runs 3 times. If you wanted it to run 4 times, you'd need to yield 4 values.

Getting return value of generator via iteration

I am having a hard time reconciling these two:
const gen = function *() {
yield 3;
yield 4;
return 5;
};
const rator = gen();
console.log(rator.next()); // { value: 3, done: false }
console.log(rator.next()); // { value: 4, done: false }
console.log(rator.next()); // { value: 5, done: true }
The above we see all 3 values, if we call next() a fourth time, we get:
{ value: undefined, done: true }
which makes sense. But now if we use it in a loop:
for(let v of gen()){
console.log('next:', v); // next: 3, next: 4
}
I guess I am confused why using the for-loop doesn't print next: 5, but calling next() on the iterator manually can get the return value. Can anyone explain why this is?
In other words, I would expect the for loop to print next: 5 but it doesn't.
For consistency, this seems to work?
const gen = function *() {
yield 3;
yield 4;
return yield 5;
};
the return keyword doesn't seem to do anything now, though.
The returned value is contained in exception.value, where exception is the StopIteration that is thrown by the generator instance when the generator frame returns.
Example:
def generator_function():
yield 0
yield 1
return "text"
value_list = []
exception_list = []
try:
a = generator_function()
while True:
value_list.append(next(a))
except StopIteration as e:
exception_list.append(e)
print(value_list)
print(exception_list)
print(type(exception_list[0]), exception_list[0])
print(type(exception_list[0].value), exception_list[0].value)
Also see Generator with return statement

How to understand javascript Generator?

I have a snippet of code
function* dataConsumer() {
console.log('Started');
console.log(`1. ${yield}`);
console.log(`2. ${yield}`);
return 'result';
}
let genObj = dataConsumer();
genObj.next();
and the running result is
Started
I don't understand why the second console.log can't output anything. Thanks for help.
Generators are build on the top of Iterators. To understand generator, you need first to understand iterator. See the Documentation.
For a one flow it runs to the nearer yield and terminates the function call. After it when you call again next it will continue from where it was terminated and works again until the nearest yield and when there are no yields or it reaches to the return statement, it just finishes its call.
function* dataConsumer() {
console.log('Started');
console.log(`1. ${yield}`); // First `next` terminates before yield. Second `next` passes the value and terminates before the next yield.
console.log(`2. ${yield}`); // Third call continues from the `yield` part and finishes the function, because we have returned
return 'result';
}
let genObj = dataConsumer();
genObj.next();
genObj.next('yield1');
genObj.next('yield2');
You also can pass parameters to the next function, which will be put in the yield statement. Also the next function returns an object about the generator state with properties value and done. If function call is not finished, it returns done as false and at the end you can see that this is set to true.
function* dataConsumer() {
console.log('Started');
const x = yield;
console.log(`1. ${x}`);
console.log(`2. ${yield}`);
return 'result';
}
let genObj = dataConsumer();
let result = genObj.next();
console.log(result);
result = genObj.next('x is yield1');
console.log(result);
result = genObj.next('yield2');
console.log(result);
genObj.next(); // This one has nothing to do
As you can pass parameters to the generator each step, you also can return a value from . You need to write something similar to return something, just replacing return with yield.
function* dataConsumer() {
yield 1;
yield 2;
yield 3;
}
let genObj = dataConsumer();
let result = genObj.next();
console.log(result);
result = genObj.next();
console.log(result);
result = genObj.next();
console.log(result);
result = genObj.next();
console.log(result);

ES6 generators mechanism - first value passed to next() goes where?

When passing parameters to next() of ES6 generators, why is the first value ignored? More concretely, why does the output of this say x = 44 instead of x = 43:
function* foo() {
let i = 0;
var x = 1 + (yield "foo" + (++i));
console.log(`x = ${x}`);
}
fooer = foo();
console.log(fooer.next(42));
console.log(fooer.next(43));
// output:
// { value: 'foo1', done: false }
// x = 44
// { value: undefined, done: true }
My mental model for the behavior of such a generator was something like:
return foo1 and pause at yield (and the next call which returns foo1 takes as argument 42)
pause until next call to next
on next yield proceed to the line with var x = 1 + 42 because this was the argument previously received
print x = 43
just return a {done: true} from the last next, ignoring its argument (43) and stop.
Now, obviously, this is not what's happening. So... what am I getting wrong here?
I ended up writing this kind of code to investigate the behavior more thoroughly (after re-re-...-reading the MDN docs on generators):
function* bar() {
pp('in bar');
console.log(`1. ${yield 100}`);
console.log(`after 1`);
console.log(`2. ${yield 200}`);
console.log(`after 2`);
}
let barer = bar();
pp(`1. next:`, barer.next(1));
pp(`--- done with 1 next(1)\n`);
pp(`2. next:`, barer.next(2));
pp(`--- done with 2 next(2)\n`);
pp(`3. next:`, barer.next(3));
pp(`--- done with 3 next(3)\n`);
which outputs this:
in bar
1. next: { value: 100, done: false }
--- done with 1 next(1)
1. 2
after 1
2. next: { value: 200, done: false }
--- done with 2 next(2)
2. 3
after 2
3. next: { value: undefined, done: true }
--- done with 3 next(3)
So apparently the correct mental model would be like this:
on first call to next, the generator function body is run up to the yield expression, the "argument" of yield (100 the first time) is returned as the value returned by next, and the generator body is paused before evaluating the value of the yield expression -- the "before" part is crucial
only on the second call to next is the value of the first yield expression computed/replaced with the value of the argument given to next on this call (not with the one given in the previous one as I expected), and execution runs until the second yield, and next returns the value of the argument of this second yield -- here was my mistake: I assumed the value of the first yield expression is the argument of the first call to next, but it's actually the argument of the second call to next, or, another way to put it, it's the argument of the call to next during whose execution the value is actually computed
This probably made more sense to who invented this because the # of calls to next is one more times the number of yield statements (there's also the last one returning { value: undefined, done: true } to signal termination), so if the argument of the first call would not have been ignored, then the one of the last call would have had to be ignored. Also, while evaluating the body of next, the substitution would have started with the argument of its previous invocation. This would have been much more intuitive imho, but I assume it's about following the convention for generators in other languages too and consistency is the best thing in the end...
Off-topic but enlightening: Just tried to do the same exploration in Python, which apparently implements generators similar to Javascript, I immediately got a TypeError: can't send non-None value to a just-started generator when trying to pass an argument to the first call to next() (clear signal that my mental model was wrong!), and the iterator API also ends by throwing a StopIteration exception, so no "extra" next() needed just to check if the done is true (I imagine using this extra call for side effects that utilize the last next argument would only result in very hard to understand and debug code...). Much easier to "grok it" than in JS...
I got this from Axel Rauschmayer's Exploring ES6, especially 22.4.1.1.
On receiving a .next(arg), the first action of a generator is to feed arg to yield. But on the first call to .next(), there is no yield to receive this, since it is only at the end of the execution.
Only on the second invocation x = 1 + 43 is executed and subsequently logged, and the generator ends.
Also had a hard time wrapping my head around generators, especially when throwing in if-statements depended on yielded values. Nevertheless, the if-statement was actually what helped me getting it at last:
function* foo() {
const firstYield = yield 1
console.log('foo', firstYield)
const secondYield = yield 3
console.log('foo', secondYield)
if (firstYield === 2) {
yield 5
}
}
const generator = foo()
console.log('next', generator.next( /* Input skipped */ ).value)
console.log('next', generator.next(2).value)
console.log('next', generator.next(4).value)
/*
Outputs:
next 1
foo 2
next 3
foo 4
next 5
*/
Everything immediately became clear once I made this realization.
Here's your typical generator:
function* f() {
let a = yield 1;
// a === 200
let b = yield 2;
// b === 300
}
let gen = f();
gen.next(100) // === { value: 1, done: false }
gen.next(200) // === { value: 2, done: false }
gen.next(300) // === { value: undefined, done: true }
But here's what actually happens. The only way to make generator execute anything is to call next() on it. Therefore there needs to be a way for a generator to execute code that comes before the first yield.
function* f() {
// All generators implicitly start with that line
// v--------<---< 100
= yield
// ^-------- your first next call jumps right here
let a = yield 1;
// a === 200
let b = yield 2;
// b === 300
}

Better understanding javascript's yield

I have the following code in my Koa app:
exports.home = function *(next){
yield save('bar')
}
var save = function(what){
var response = redis.save('foo', what)
return response
}
But I get the following error: TypeError: You may only yield a function, promise, generator, array, or object, but the following object was passed: "OK"
Now, "ok" is the response from the redis server, which makes sense. But I cannot fully grasp the concept of generators for this kinds of functions. Any help?
You don't yield save('bar') because SAVE is synchronous. (Are you sure you want to use save?)
Since it's synchronous, you should change this:
exports.home = function *(next){
yield save('bar')
}
to this:
exports.home = function *(next){
save('bar')
}
and it will block execution until it's finished.
Almost all other Redis methods are asynchronous, so you would need to yield them.
For example:
exports.home = function *(next){
var result = yield redis.set('foo', 'bar')
}
Yield is supposed to be used inside a generator function according to the documentation.
The purpose is to return the result of an iteration to be used in the next iteration.
Like in this example (taken from the documentation):
function* foo(){
var index = 0;
while (index <= 2) // when index reaches 3,
// yield's done will be true
// and its value will be undefined;
yield index++;
}
var iterator = foo();
console.log(iterator.next()); // { value:0, done:false }
console.log(iterator.next()); // { value:1, done:false }
console.log(iterator.next()); // { value:2, done:false }
console.log(iterator.next()); // { value:undefined, done:true }

Categories