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);
Related
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.
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.
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).
Learn Generators - 4 » CATCH ERROR!
The solution uses a for loop but I just couldn't find anything in MDN - Iteration Protocols that refers to yield within callbacks.
I'm going to guess the answer is just don't do that but thanks in advance if anyone has the time or inclination to provide an explanation!
Code:
function *upper (items) {
items.map(function (item) {
try {
yield item.toUpperCase()
} catch (e) {
yield 'null'
}
}
}
var badItems = ['a', 'B', 1, 'c']
for (var item of upper(badItems)) {
console.log(item)
}
// want to log: A, B, null, C
Error:
⇒ learn-generators run catch-error-map.js
/Users/gyaresu/programming/projects/nodeschool/learn-generators/catch-error-map.js:4
yield item.toUpperCase() // error below
^^^^
SyntaxError: Unexpected identifier
at exports.runInThisContext (vm.js:73:16)
at Module._compile (module.js:443:25)
at Object.Module._extensions..js (module.js:478:10)
at Module.load (module.js:355:32)
at Function.Module._load (module.js:310:12)
at Function.Module.runMain (module.js:501:10)
at startup (node.js:129:16)
at node.js:814:3
Even my editor knows this is a terrible idea...
Disclaimer: I'm the author of Learn generators workshopper.
Answer by #slebetman is kinda correct and I also can add more:
Yes, MDN - Iteration Protocol doesn't refer directly about yield within callbacks.
But, it tell us about importance from where you yield item, because you can only use yield inside generators. See MDN - Iterables docs to find out more.
#marocchino suggest just fine solution iterate over Array that was changed after map:
function *upper (items) {
yield* items.map(function (item) {
try {
return item.toUpperCase();
} catch (e) {
return null;
}
});
}
We can do it, because Array has iteration mechanism, see Array.prototype[##iterator]().
var bad_items = ['a', 'B', 1, 'c'];
for (let item of bad_items) {
console.log(item); // a B 1 c
}
Array.prototype.map doesn't have default iteration behavior, so we couldn't iterate over it.
But generators is not just iterators. Every generator is an iterator, but not vice versa. Generators allows you to customize iteration (and not only) process by calling yield keyword. You can play and see the difference between generators/iterators here:
Demo: babel/repl.
One problem is yield yields just one level to the function's caller. So when you yield in a callback it may not do what you think it does:
// The following yield:
function *upper (items) { // <---- does not yield here
items.map(function (item) { // <----- instead it yields here
try {
yield item.toUpperCase()
} catch (e) {
yield 'null'
}
}
}
So in the code above, you have absolutely no access to the yielded value. Array.prototype.map does have access to the yielded value. And if you were the person who wrote the code for .map() you can get that value. But since you're not the person who wrote Array.prototype.map, and since the person who wrote Array.prototype.map doesn't re-yield the yielded value, you don't get access to the yielded value(s) at all (and hopefully they will be all garbage collected).
Can we make it work?
Let's see if we can make yield work in callbacks. We can probably write a function that behaves like .map() for generators:
// WARNING: UNTESTED!
function *mapGen (arr,callback) {
for (var i=0; i<arr.length; i++) {
yield callback(arr[i])
}
}
Then you can use it like this:
mapGen(items,function (item) {
yield item.toUpperCase();
});
Or if you're brave you can extend Array.prototype:
// WARNING: UNTESTED!
Array.prototype.mapGen = function *mapGen (callback) {
for (var i=0; i<this.length; i++) {
yield callback(this[i])
}
};
We can probably call it like this:
function *upper (items) {
yield* items.mapGen(function * (item) {
try {
yield item.toUpperCase()
} catch (e) {
yield 'null'
}
})
}
Notice that you need to yield twice. That's because the inner yield returns to mapGen then mapGen will yield that value then you need to yield it in order to return that value from upper.
OK. This sort of works but not quite:
var u = upper(['aaa','bbb','ccc']);
console.log(u.next().value); // returns generator object
Not exactly what we want. But it sort of makes sense since the first yield returns a yield. So we process each yield as a generator object? Lets see:
var u = upper(['aaa','bbb','ccc']);
console.log(u.next().value.next().value.next().value); // works
console.log(u.next().value.next().value.next().value); // doesn't work
OK. Let's figure out why the second call doesn't work.
The upper function:
function *upper (items) {
yield* items.mapGen(/*...*/);
}
yields the return value of mapGen(). For now, let's ignore what mapGen does and just think about what yield actually means.
So the first time we call .next() the function is paused here:
function *upper (items) {
yield* items.mapGen(/*...*/); // <----- yields value and paused
}
which is the first console.log(). The second time we call .next() the function call continue at the line after the yield:
function *upper (items) {
yield* items.mapGen(/*...*/);
// <----- function call resumes here
}
which returns (not yield since there's no yield keyword on that line) nothing (undefined).
This is why the second console.log() fails: the *upper() function has run out of objects to yield. Indeed, it only ever yields once so it has only one object to yield - it is a generator that generates only one value.
OK. So we can do it like this:
var u = upper(['aaa','bbb','ccc']);
var uu = u.next().value; // the only value that upper will ever return
console.log(uu.next().value.next().value); // works
console.log(uu.next().value.next().value); // works
console.log(uu.next().value.next().value); // works
Yay! But, if this is the case, how can the innermost yield in the callback work?
Well, if you think carefully you'll realize that the innermost yield in the callback also behaves like the yield in *upper() - it will only ever return one value. But we never use it more than once. That's because the second time we call uu.next() we're not returning the same callback but another callback which in turn will also ever return only one value.
So it works. Or it can be made to work. But it's kind of stupid.
Conclusion:
After all this, the key point to realize about why yield doesn't work the way we expected is that yield pauses code execution and resumes execution on the next line. If there are no more yields then the generator terminates (is .done).
Second point to realize is that callbacks and all those Array methods (.map, .forEach etc.) aren't magical. They're just javascript functions. As such it's a bit of a mistake to think of them as control structures like for or while.
Epilogue
There is a way to make mapGen work cleanly:
function upper (items) {
return items.mapGen(function (item) {
try {
return item.toUpperCase()
} catch (e) {
return 'null'
}
})
}
var u = upper(['aaa','bbb','ccc']);
console.log(u.next().value);
console.log(u.next().value);
console.log(u.next().value);
But you'll notice that in this case we return form the callback (not yield) and we also return form upper. So this case devolves back into a yield inside a for loop which isn't what we're discussing.
You can use another method by "co - npm": co.wrap(fn*)
function doSomething(){
return new promise()
}
var fn = co.wrap(function* (arr) {
var data = yield arr.map((val) => {
return doSomething();
});
return data;
});
fn(arr).then(function (val) {
consloe.log(val)
});
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 }