Why does this generator skip a yield outside the try block? - javascript

Background
I am experimenting with how Generator.prototype.throw() works and made this example:
var myGen = function *() {
try{
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
}
catch(err) {
console.log(err);
}
yield 7;
yield 8;
yield 9;
}
var myIterator = myGen();
console.log(myIterator.next());
console.log(myIterator.next());
console.log(myIterator.next());
myIterator.throw(new Error('Bullocks!'));
console.log(myIterator.next());
console.log(myIterator.next());
console.log(myIterator.next());
which at runtime results in the following:
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
[Error: Bullocks!]
{ value: 8, done: false }
{ value: 9, done: false }
{ value: undefined, done: true }
Question
I can understand that yield 4 and the remaining part of the try block is skipped after throwing an error.
But why does the generator skip yield 7?

It doesn't skip yield 7. When you call throw(), the control flow goes into the catch block, the error is logged, and then the execution continues until the next yield upon which a new iterator result object { value: 7, done: false } is returned.
It's just that in your code you don't console.log this one particular result object. Try:
console.log(myIterator.throw(new Error('Bullocks!')));
This is explained in step 11 and 13 of the spec:
Resume the suspended evaluation of genContext using abruptCompletion as the result of the operation that suspended it. Let result be the completion record returned by the resumed computation.
(…)
Return Completion(result).

Related

JavaScript ES6 Generator Catch Error Before Next Method

I start learn about Iterator/Generators and sometimes I saw some weird parts. Currently I don't understand why my code work like this.
In bellow code sample is my main code.
function* makeGenerator() {
try {
yield 1;
yield 1;
yield 1;
} catch (e) {
console.log(e);
}
}
const generator = makeGenerator();
When I use next method before throw this code work normally.
console.log(generator.next()); // { done: false, value: 1 }
console.log(generator.throw("WTF?")); // "WTF"
console.log(generator.next()); // { done: true, value: undefined }
function* makeGenerator() {
try {
yield 1;
yield 1;
yield 1;
} catch (e) {
console.log(e);
}
}
const generator = makeGenerator();
console.log(generator.next()); // { done: false, value: 1 }
console.log(generator.throw("WTF?")); // "WTF"
console.log(generator.next()); // { done: true, value: undefined }
But when I want to use throw method before next method I don't understand why next methods not work?
console.log(generator.throw("WTF?")); // "WTF"
console.log(generator.next());
console.log(generator.next());
function* makeGenerator() {
try {
yield 1;
yield 1;
yield 1;
} catch (e) {
console.log(e);
}
}
const generator = makeGenerator();
console.log(generator.throw("WTF?")); // "WTF"
console.log(generator.next());
console.log(generator.next());
When this statement is executed:
const generator = makeGenerator();
...none of the code in the makeGenerator function body is executed yet, and so its try/catch block is not yet in effect. Only when you execute generator.next(), the code in the makeGenerator function body will start executing up until the next yield. At that point the try/catch block is in effect.
So... if you call generator.throw() before the first generator.next() call, you are triggering an exception that is not handled by the makeGenerator code. Your code breaks with an unhandled exception. As a consequence nothing that follows that generator.throw() (in your second code version) is executed.
The proper way of throwing errors via generators is to wrap them inside a try...catch because with not catching the error you're creating an error there, then, it falls through to the outer calling code (if any) and, if uncaught, kills the script.
function* makeGenerator() {
try {
yield 1;
yield 1;
yield 1;
} catch (e) {
console.log(e);
}
}
const generator = makeGenerator();
console.log(generator.next());
try {
generator.throw(new Error("WHOOPS"));
} catch(e) {
console.log(e); // shows the error
}

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.

console.log not being called from generator function

I'm using generator functions in javascript for the first time and running in to some interesting problems.
Code:
import moment from 'moment';
export default function recur(quantity, units) {
console.log('TESTING 1');
function* recurGenerator(startDate, maxDate) {
console.log('TESTING 2');
if (maxDate === undefined) {
this.throw('Argument maxDate is undefined');
}
let nextDate = moment(startDate).clone();
maxDate = moment(maxDate);
for (;;) {
nextDate = moment(nextDate).clone().add(quantity, units);
if (nextDate.isAfter(maxDate)) yield null;
yield nextDate;
}
}
return recurGenerator;
}
The "TESTING 2" console.log never gets called. It also doesn't raise an error if I don't pass maxDate to the generator function. This must be something about generator's I'm missing.
EDIT TO SHOW USAGE
recur(1, 'day')(moment())
It seems that next needs to be called to run code before the first yield?
In a generator function, the code before the first yield statement gets executed not sooner than the generator proceeds to that point:
let a = function * () {
console.log(1);
yield 2;
yield 3;
}
let b = a(); // no console output!
let c = b.next(); // prints 1 to the console
c // { value: 2, done: false }

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.

ES6 yield (yield 1)(yield 2)(yield 3)()

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;
}

Categories

Resources