JavaScript ES6 Generator Catch Error Before Next Method - javascript

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
}

Related

Wait for an async operation inside asyncIterator generator function

I have this Queue class (not the real implementation, but it exemplifies my point):
class Queue {
constructor() {
this._arr = [];
}
async push(elem) {
this._arr.push(elem);
}
async pop() {
return this._arr.pop();
}
*[Symbol.asyncIterator]() {
do {
let res = await this.pop(); // here is the problem
if (res) yield res;
} while (res);
}
}
It's just a wrapper for a Javascript Array with the exception that its methods return a Promise.
What I want to do is to yield conditionally based on the return value of the pop() method, which I cannot do because await is not a valid operation inside an asyncIterator generator function.
I thought about having a flag which is set in the previous iteration:
*[Symbol.asyncIterator]() {
let continue = true;
do {
yield this.pop().then(v => {
if (!v) continue = false;
return v
});
} while (continue);
}
But this would still return a undefined value in the last execution of pop().
I could handle this in the calling code by checking for a undefined value as a signal of the end of the iteration, but I was wondering if there was a better approach at tackling this problem.
You can use an async generator function (MDN docs missing, but see e.g. this article) for the implementation of the [Symbol.asyncIterator]() method:
async *[Symbol.asyncIterator]() { /*
^^^^^ */
while (this._arr.length) {
yield await this.pop(); // no longer a problem
}
}

Calling functions inside generator functions, but "Generator is already running"

I am coding a project for a simple football game in JavaScript. When a player hikes the ball, I am attempting to run a series of functions in order to validate a legal snap. To do this, I am using a generator function, so that I can organize all the functions that run, as the order in which they run is important. Essentially, I run the generator function once using the snap() function, and then at the conclusion of each Check() function, I either return validateSnap.next() if it is a legal snap, or a fail function to exit out of the generator and handle an illegal snap. Here is a simplified version of my code below:
function* snapProtocol() {
yield check1();
yield check2();
yield check3();
yield check4();
yield check5();
yield play();
}
let validateSnap = snapProtocol()
function snap() {
validateSnap.next();
}
function check1() {
let meetsCriteria = true;
if (meetsCriteria) {
validateSnap.next();
} else {
handleError();
}
}
I am receiving a "Generator is already running" error. I presume this is because the check1 function has not finished, but when I add a callback function, I get the same error. Why is this occurring? Is there a simpler method to accomplish this? Previously, I would run each check function and then have it return either a true or false value, with true if it was a legal snap to go to the next check function, or false to stop the execution of the initial function. This required to me declare a bunch of variables as well as have an if statement after every function, so I was looking for a cleaner approach.
You are try to call next() inside check function which is in progress. You can't call next() during this function end. Shortly you can not call next() inside function which is returned from generator.
Maybe this example can be helpful.
function* snapProtocol() {
yield check1();
yield check1();
yield check1();
yield check1();
yield check1();
}
let validateSnap = snapProtocol();
function snap() {
let result = null
do {
result = validateSnap.next()
if ( result.done ) {
// all creterias was meet and play could be call
break;
}
if ( result.value ) {
// meets criteria and cen check another one
continue
} else {
// doesn't meet createria and somthing should happend here
break;
}
} while( !result.done )
}
function check1() {
let meetsCriteria = true
if (meetsCriteria) {
return true
} else {
return false
}
}

Advantages of using race in sagas

I have a pattern in my app regarding redux-saga, that for asynchro calls I have two functions - the first one is listening to some specified action and the second one is making the call to the api.
Listener function:
function* userJoinListener() {
while (true) {
const { user } = yield take(UserJoinRequest);
const promiseRace = yield race({
res: call(userJoinFunction, user),
err: take(UserJoinError),
});
if (promiseRace.res) {
// code if success
} else {
// code if fail
}
}
}
Api call executing function:
function* userJoinFunction(user) {
try {
return yield call(userJoin, user);
} catch (err) {
yield put(userJoinFail);
}
}
My question is: what is the advantage of using race here exactly? To be honest, I could just use that second function in the same place as race is and it would work as expected:
function* userJoinListener() {
while (true) {
const { user } = yield take(UserJoinRequest);
try {
// code if success
return yield call(userJoin, user);
} catch (err) {
// code if fail
yield put(userJoinFail);
}
}
}
Thank you :)
Related question: Difference between Promise.race() and try/catch in redux-saga
Update:
Advantages:
ability to cancel request
Indeed using race here is unnecessary complex.
If you are handling errors or cancellation inside the called saga (userJoinFunction) itself, then just use try..catch/cancel as that is more straightforward.
If on the other hand you need to cancel the saga when something happens from the outside (timeout, user action) then it makes sense to use the race effect.

javascript: How to tell if I'm within a generator function?

Heyo! I'm playing with function*'s and yield's. I've noticed that (In NodeJS) anyway, when I try to call yield when I'm not within a function*, yield is undefined. Though yield is keyword so I can't exactly check if yield === undefined.
So what I am asking is, how can I tell if my code is currently running through a function*?
Since generators aren't constructable, you can try using new GeneratorFunction(), and it will throw a TypeError in case it's a generator function.
function* gen() {
yield 1;
}
function fn() {
return 1;
}
function isGenerator(fn) {
try {
new fn();
return false;
} catch (err) {
return true;
}
}
console.log(isGenerator(gen)); // true
console.log(isGenerator(fn)); // false
You can also check for Object.getPrototypeOf(gen), and it will return a Generator constructor. Then you can do for example:
console.log(Object.getPrototypeOf(gen).prototype[Symbol.toStringTag]); // Generator
To find out whether you are currently within a GeneratorFunction, you could check the function's constructor:
function* generator() {
// Recommended:
console.log(Object.getPrototypeOf(generator).constructor === Object.getPrototypeOf(function*(){}).constructor);
// Deprecated:
console.log(Object.getPrototypeOf(arguments.callee).constructor === Object.getPrototypeOf(function*(){}).constructor);
}
generator().next();

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

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).

Categories

Resources