Invoking a generator directly does not work as expected [duplicate] - javascript

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.

Related

What happens to argument value of first .next() in generator function* [duplicate]

This question already has answers here:
In ES6, what happens to the arguments in the first call to an iterator's `next` method?
(3 answers)
Closed 6 months ago.
Consider this generator function.
Why is the argument of the first call to .next() essentially lost? It will yield and log each of the letter strings but skips "A". Can someone offer an explanation. Please advise on a way that I can access each argument each argument of the .next() method and store it in an array within the generator function?
function* gen(arg) {
let argumentsPassedIn = [];
while (true) {
console.log(argumentsPassedIn);
arg = yield arg;
argumentsPassedIn.push(arg);
}
}
const g = gen();
g.next("A"); // ??
g.next("B");
g.next("C");
g.next("D");
g.next("E");
This is a limitation of generator functions. If you really want to be able to use the first argument, you can construct your own iterator manually.
const gen = () => {
const argumentsPassedIn = [];
const makeObj = () => ({
done: false,
value: undefined,
next: (arg) => {
argumentsPassedIn.push(arg);
console.log(argumentsPassedIn);
return makeObj();
},
});
return makeObj();
}
const g = gen();
g.next("A");
g.next("B");
g.next("C");
g.next("D");
g.next("E");
As per the docs
The first call of next executes from the start of the function until the first yield statement
So when you call the first next, it just calls the generator function from start to till the first yield and then returns and from next call it works normal.
To make you code work, you should try like this.
function* gen(arg) {
let argumentsPassedIn = [];
while (true) {
console.log(argumentsPassedIn);
arg = yield arg;
argumentsPassedIn.push(arg);
}
}
const g = gen();
g.next()
g.next("A"); // ??
g.next("B");
g.next("C");
g.next("D");
g.next("E");
The parameter passed to the first call to .next() is ignored as of ES2022. This is because the first call to .next() runs the function until the first yield or return is encountered and the following calls will make yield operator return the value passed as a parameter. This is usually solved by calling .next() unconditionally after calling the generator function.
There is, however, a stage 2 proposal that aims to solve this problem by introducing new syntax to get the value passed to the .next() method that most recently resumed execution of the generator.

Receiving returns from asynchronous functions

I've searched through multiple questions, but I have failed to find one that matches my case.
The following is the simplified version of my code:
function one() {
let a;
// does some fetching; takes about 1s
a = 1; // value is obtained from above line
return a;
}
function two() {
let b;
// does some fetching; takes about 2s
b = 2; // value is obtained from above line
return b;
}
function three() {
let c;
// does some fetching; takes about 0.5s
c = 3; // value is obtained from above line
return c;
}
var variables = [];
function final() {
variables.push(one());
variables.push(two());
variables.push(three());
}
final();
console.log(variables);
What above code gives is [], while the desired output is [1, 2, 3](the order doesn't matter).
I've recognized that the solution is deeply related to async/await and promises(return new Promise(...)), but the implementation mostly fails, either because the await keyword does nothing, or promise comes out <pending>.
Also, through several experiments I've found that the return keyword also runs asynchronously, which means that one(), two() and three() almost certainly returns undefined.
Is there a way to get the return values in a synchronous manner?
Edit 1: The original code works as follows: (1)three different functions fetches json from different APIs which takes some seconds, (2) each functions returns the parsed data from the json, then (3) let the returned value be pushed onto var variables = []; array. I should have added this text in the first place.

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

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
}

Restarting a Generator in Javascript

In node (0.11.9, with the --harmony flag), how do I restart a generator after it finishes?
I tried doing generator.send(true); but it says the send() method doesn't exists.
You don't restart a generator. Once it has completed, it has finished its run like any other function. You need to recreate the generator to run again.
var count = function*(){ yield 1; return 2;};
var gen = count();
var one = gen.next();
var two = gen.next();
// To run it again, you must create another generator:
var gen2 = count();
The other option would be to design your generator such that it never finishes, so you can continue calling it forever. Without seeing the code you are talking about, it is hard to make suggestions though.
A bit late, but this is just a FYI.
At the moment, the send method is not implemented in Node, but is in Nightly (FF) - and only in some way.
Nightly:
If you declare your generator without the *, you'll get an iterator that has a send method:
var g = function() {
var val = yield 1; // this is the way to get what you pass with send
yield val;
}
var it = g();
it.next(); // returns 1, note that it returns the value, not an object
it.send(2); // returns 2
Node & Nightly:
Now, with the real syntax for generators - function*(){} - the iterators you produce won't have a send method. BUT the behavior was actually implemented in the next method. Also, note that it was never intended for send(true); to automatically restart your iterator. You have to test the value returned by yield to manually restart it (see the example in the page you linked). Any value, as long as it's not a falsy one, could work. See for yourself:
var g = function*() {
var val = 1;
while(val = yield val);
}
var it = g();
it.next(); // {done: false, value: 1}
it.next(true); // {done: false, value: true}
it.next(2); // {done: false, value: 2}
it.next(0); // {done: true, value: undefined}

Categories