ES6 yield (yield 1)(yield 2)(yield 3)() - javascript

function* generatorFunction() {
yield (yield 1)(yield 2)(yield 3)();
}
var iterator = generatorFunction();
// [1, 2, 3]
var iteratedOver = [iterator.next().value, iterator.next().value, iterator.next().value];
I'm not sure how this works.
yield doesn't return a function reference, so what are the parenthetical statements like (yield 2) doing - are they fat arrow anonymous functions without bodies? How are they called using partial application like that?
I'm missing something here, can someone explain?
Update: Tried on three browsers, Chrome 50.0.2661.86, Safari 9.1 (50.0.2661.86), Firefox 44.0.2, all perform without errors.
ESFiddle also executes it without errors.
Commenters report Babel executes without errors as well.
The source of the question is from http://tddbin.com/#?kata=es6/language/generator/send-function, the second kata.

I'm not sure how this works.
Uh, yeah, it shouldn't work. It's only working because of a bug in Babel.
yield doesn't return a function reference, so what are the parenthetical statements like (yield 2) doing - are they fat arrow anonymous functions without bodies? How are they called using partial application like that?
No, it's really just standard function application, no magic. yield could return a function reference, and when it does this might work. When it doesn't, it will throw an exception on the third .next() call.
As an example for a working version:
function* generatorFunction() {
yield (yield 1)(yield 2)(yield 3)();
}
var test = (a) => {
console.log(a);
return (b) => {
console.log(b);
return (c) => {
console.log(c);
return 4;
};
};
};
var iterator = generatorFunction();
iterator.next(); // {value: 1, done: false}
iterator.next(test); // {value: 2, done: false}
iterator.next("a"); // "a" {value: 3, done: false}
iterator.next("b"); // "b" undefined {value: 4, done: false}
iterator.next("d"); // {value: undefined, done: true}
So how does this work? Those nested/chained yield statements should better be written as
function* generatorFunction() {
let fn1 = yield 1;
let a = yield 2;
let fn2 = fn1(a);
let b = yield 3;
let fn3 = fn2(b);
let res = fn3();
let d = yield res;
return undefined;
}
Commenters report Babel executes without errors as well.
That's because of a babel bug. If you check the transpiler output, it actually behaves like
function* generatorFunction() {
let fn1 = yield 1;
let a = yield 2;
let b = yield 3;
// these are no more executed with only 3 `next` calls
let fn2 = fn1(a);
let fn3 = fn2(b);
let res = fn3();
let d = yield res;
return undefined;
}

Related

Unexpected token '*' Expected an opening '(' before a function's parameter list

iOS9 don't seems to like javascript generators function*. I'm currently trying to get rid of a promise problem on iOS and my code looks like this :
...
myFunction: function() {
return this.spawn(function*() {
yield MyApp.function();
});
return;
},
...
which leads to the following error on iOS 9:
Unexpected token '*' Expected an opening '(' before a function's
parameter list
Do you know an alternative that would be compatible ?
iOS 10 supports generators. If you're stuck with iOS 9, then you'll have to implement your own fake generator. The generator protocol has the next, return, throw, and [Symbol.iterator] methods. You can implement only the ones you need. E.g., here's an implementation of [Symbol.iterator]:
let createGenerator = values => () => ({
[Symbol.iterator]() {
let i = 0;
return {
next: () => ({
value: values[i++],
done: i > values.length
}),
// next
// return
// throw
}
}
});
let realGenerator = function*() {
yield 1;
yield 2;
yield 3;
yield 4;
};
let fakeGenerator = createGenerator([1, 2, 3, 4]);
console.log(...realGenerator());
console.log(...fakeGenerator());

JavaScript: Setting value using yield in nested generators

function* test(action) {
const subgenerator = function*() {
const subgeneratorVariable = yield '1';
console.log('subgeneratorVariable', subgeneratorVariable);
};
for (const generatedValue of subgenerator()) {
const result = yield generatedValue;
console.log('result', result);
}
}
const gen = test();
console.log(gen.next());
console.log(gen.next('a'));
This returns:
{value: "1", done: false}
result a
subgeneratorVariable undefined
{value: undefined, done: true}
So I don't have access to yield results in the nested generator. However!
In this case:
function* test(action) {
const subgenerator = function*() {
const subgeneratorVariable = yield '1';
console.log('subgeneratorVariable', subgeneratorVariable);
const subgeneratorVariable2 = yield '2';
console.log('subgeneratorVariable2', subgeneratorVariable2);
};
const result = yield* subgenerator();
console.log('result', result);
}
const gen = test();
console.log(gen.next());
console.log(gen.next('1'));
console.log(gen.next('2'));
The result is:
{value: "1", done: false}
subgeneratorVariable a
{value: "2", done: false}
subgeneratorVariable2 b
result undefined
{value: undefined, done: true}
So the situation is reversed. Now I have access to yielded value ONLY in nested generator.
Is there any way of passing values/results between these two generators?
yield is going to return what you are passing to next as a parameter, not the expression at right side; for...of internally is calling next without parameters so you are getting undefined.
Maybe this example helps you to understand with a do...while instead of for...of:
function* test(action) {
const subgenerator = function*() {
let subgeneratorVariable;
subgeneratorVariable = yield '1';
console.log("subgeneratorVariable: " + subgeneratorVariable);
subgeneratorVariable = yield '2';
console.log("subgeneratorVariable: " + subgeneratorVariable);
};
const sub = subgenerator();
let value;
do {
value = yield sub.next(value).value;
console.log("value: " + value);
} while(value);
}
const gen = test();
let value;
value = gen.next().value;
value = gen.next(value).value;
value = gen.next(value).value;
The result is:
value: 1
subgeneratorVariable: 1
value: 2
subgeneratorVariable: 2
As you can see, if you want to keep the values between the generators you must pass the result values to the generators next function.
After upgrading myself with the new yield in javascript and some efforts reading your examples, I think I figured out what you are trying to do and what you are doing wrong.
Your 1st attempt was correct. But you placed your console.log incorrectly which mislead you into thinking it was wrong.
I rewrote it so you get better insight of what happens when yield-ing.
function* generator() {
function* subgenerator() {
yield '1';
yield '2';
};
for (let generatedValue of subgenerator()) {
// Use generatedValue here ...
console.log("Inside generator:",generatedValue);
// .. and finally yield it to your main program
yield generatedValue;
}
}
let gen = generator();
console.log("Outside generator:",gen.next());
console.log("Outside generator:",gen.next());
console.log("Outside generator:",gen.next());
In your second example, you delegate the generation to the subgenerator. Doing so, you cannot get the value in your generator itself. Because the value is yield-ed and your generator will resume after the subgenerator is finished generating. It explains why, your generator only sees undefined.

Using return in ES6 generator function

I am having trouble finding out what happens if you use a return statement instead of yield.
function *gen(){
const val = yield someAsyncFn();
assert.equal(val,4);
return val;
}
how does the return behave differently from the yield? I assume the return acts as a normal return statement, but the context of a generator function, does it also call gen.return() as well? Sort of confusing.
Perhaps the above is merely identical to this?
function *gen(){
const val = yield someAsyncFn();
assert.equal(val,4);
yield val;
}
return deliveres a return value for an iterators last iteration (when done equals true).
I've simplified your example a bit, since the async operation doesn't seem to be relevant to the question:
function *gen(){
const val = yield 4;
return val * 2;
}
var it = gen();
var val = it.next(); // { value: 4, done: false }
console.log(val.value); // 4
var res = it.next(val.value); // { value: 8, done: true }
console.log(res.value); // 8
Whereas without a return value, on the last iteration you will return a value of undefined:
function *gen2(){
const val = yield 4;
yield val * 2;
}
var it2 = gen2();
var val2 = it2.next(); // { value: 4, done: false }
console.log(val2.value); // 4
var res2 = it2.next(val2.value); // { value: 8, done: false }
console.log(res2.value); // 8
it2.next(); // { value: undefined, done: true }
Sidenote: As a rule of thumb, there is always one more next call then there are yield statements, which is why there is one more next call in the second example.
Let's say you're using a generator-runner like co, then the value you get after finishing the generator would be the value you return:
co(function* () {
var result = yield Promise.resolve(true);
return result;
}).then(function (value) {
console.log(value); // value equals result
}, function (err) {
console.error(err.stack); // err equals result
});
Important: If you are iterating through an iterator, using a for ... of loop or something like Array.from, the return value is going to be ignored (Since you are doing async operations, this is probably not the case anyway):
function *gen(){
const val = yield 4;
return val * 2;
}
for (let value of gen()) {
console.log(value);
}
// 4
In the end, calling a generator just creates an iterator. Whether the final value that the iterator returns is relevant, depends entirely on how you use it.
In addition to the thorough answer by #nils, there is one additional way to capture the return value of a generator function, namely as the value of yield* (necessarily inside another generator function):
function* arrayGenerator(arr) {
for (const element of arr)
yield element
return arr.length
}
function* elementsFollowedByLength(arr) {
const len = yield* arrayGenerator(arr);
yield len;
}
Note the first generator function which returns a value, after it is done yielding the array elements.
The second generator function, through yield*, causes the first generator function to yield all its values. When the first generator function is done and returns, that return value becomes the value of the yield* expression.

Javascript yield from the function nested inside generator

This code generates an error:
function *giveNumbers() {
[1, 2, 3].forEach(function(item) {
yield item;
})
}
This is probably because yield is inside a function that is not a generator. Is there an elegant way to overcome this? I mean other than:
function *giveNumbers() {
let list = [1, 2, 3];
for (let i = 0; i < list.length; i++) {
yield list[i];
}
}
This is probably because yield is inside a function that is not a generator.
Yes. You cannot use yield from callbacks.
Is there an elegant way to overcome this?
Depends on the use case. Usually there is zero reason to actually want to yield from a callback.
In your case, you want a for…of loop, which is superior to .forEach in almost every aspect anyway:
function *giveNumbers() {
for (let item of [1, 2, 3])
yield item;
}
you can use the yield * syntax.
function *giveNumbers() {
yield * [1, 2, 3].map(function(item) {
return item;
})
}
yield returns the result to the caller.
let's assume the forEach callback is a generator (it's not a problem to set a costume generator there) - it means tha when the callback yield the result - it yields it back to forEach.
Basically, in your question what you attemp to do is:
callback -> yields to forEach -> yields to giveNumbers -> yields to caller
So, forEach should yield the result back to giveNumbers. but since forEach doesn't work like this, it's impossible without re-prototype arrays with costume forEach.Actually, you second snippet is the most elegant to begin with.
You can also use while and pass arguments as such (Demo)
function *giveNumbers(array, start) {
var index = start || 0;
while(array.length > index + 1) {
yield array[index++];
}
return array[index];
}
var g = giveNumbers([1,2,3], 0);
var finished = false;
while(!finished) {
var next = g.next();
console.log(next.value);
if(next.done) {
finished = true;
}
}

Are generators really intrusive

As I understand the current spec for Javascript generators, you have to mark functions containing yields explicitly.
I wonder what the rationate behind this is.
If this is true, it would force people to write:
let thirdfunc = function*() {
let value = 5;
let other = yield 6;
return value;
};
let secondfunc = function*() {
yield thirdfunc();
};
let firstfunc = function*() {
yield secondfunc();
};
let gen = function*() {
// some code
// more code
yield firstfunc();
// and code
};
let it = gen();
while( !it.done() ) {
it.next();
};
Which means, generators would spread like cancer in a codebase.
While in the end, to the developer only yielding and handling the iterator is really interesting.
I would find it much more practical, to just define, where I want to handle the iteration.
let thirdfunc = function() {
let value = 5;
let other = yield 6; // Change 1: incorporate yield
return value;
};
let secondfunc = function() {
thirdfunc();
};
let firstfunc = function() {
secondfunc();
};
let gen = function*() { // Change 2: at this level I want to deal with descendant yields
// some code
// more code
firstfunc();
// and code
};
// Change 3: Handle iterator
let it = gen();
while( !it.done() ) {
it.next();
}
If the browser then has to turn everything between the yield call and the generator handler (firstfunc, secondfunc, thirdfunc) into promise / future form, that should work automagically and not be the business of Javascript developers.
Or are there really good arguments for not doing this?
I described the rationale for this aspect of the design at http://calculist.org/blog/2011/12/14/why-coroutines-wont-work-on-the-web/ -- in short, full coroutines (which is what you describe) interfere with the spirit of JavaScript's run-to-completion model, making it harder to predict when your code can be "pre-empted" in a similar sense to multithreaded languages like Java, C#, and C++. The blog post goes into more detail and some other reasons as well.

Categories

Resources