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
}
}
Related
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.
I want to call ajax request 3 times sequentially (requests have same URL) as describe:
Request 1 -> Done -> Request 2 -> Done -> Request 3 -> Done -> Do something
In each callback functions, if have any conditions is correct, the loop will be end.
I can write simple script to do, like:
$.ajax().then(function () {
if (condition) {}
else {
$.ajax().then(function () {
if () else () {
$.ajax().then(function () {
if () else ();
});
}
});
}
});
But if its not only 3 times (ex n times), it looks so so stupid.
How I make many requests using Promise?
Thanks a lots!
Using vanilla promises, a "recursive function" would work. Simply supply the number of times (plus one) on each recursive call.. the recursive function will itself return a new promise if there is additional work to be done.
var maxCalls = 3
function makeCall(timesCalled) {
// we're done - don't return a chained promise
if (timesCalled > maxCalls)
return;
return $.ajax().then(function () {
// likewise - don't return chained promise
// (it may make sense to move this up outside)
if (condition)
return;
// return the next promise, or not if done
return makeCall(timesCalled + 1);
});
}
var promiseResolvedAtEnd = makeCall(1);
Classic JavaScript problem.
You can look into "generators" with yield keyword.
It's a new feature in EcmaScript 6, made to make asynchronous calls look/feel synchronous.
That's exactly what avoids the "Ajax Christmas-tree" you've stumbled upon, without breaking asynchronous nature of your calls.
Now they might look a little convoluted at first. But once you get into it, it should become second nature. Imagine never having to deal with the recursive web of $.ajax events again.
Note: I myself don't fully understand them yet. But in my experiments so far I got the code you're seeing below. I think you have to call wrapper eventually by passing generator to it. If anyone else can shed more light on this, it would be appreciated.
Here is an example:
<script>
// -- I: Basic Example
function generator()
{
yield 0 + 1;
yield 0 + 2;
yield 0 + 3;
yield "Hello";
return "Done.";
}
var gen = generator();
// Iterate through return values until done
gen.next(); // 1
gen.next(); // 2
gen.next(); // 3
gen.next(); // "Hello."
gen.next(); // "Done."
// -- II: Take this principle to a real-case scenario:
window.onload = function() {
// Generator - asynchronous code that looks synchronous
function generator() {
var tweets = yield $.getJSON("data/tweets.json");
var friends = yield $.getJSON("data/friends.json");
var followers = yield $.getJSON("data/followers.json");
}
// Wrap
function wrapper(generator) {
var gen = generator();
// Handle
function handle(yielded) {
if (!yielded.done) {
// Then
yielded.value.then(function(data) {
// Next
return handle(gen.next(data));
});
}
}
}
}
</script>
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();
How does work app.use in KOA?
When I set some generator inside app.use, everything works perfect.
How can I do the same elsewhere?
When I just execute generator manual:
var getRelationsList = function *() {
var res = yield db.relations.find({});
console.log({'inside: ': res});
}
console.log({'outside: ': getRelationsList().next()});
getRelationsList().next();
I'm getting just { 'outside: ': { value: [Function], done: false } }
This what I expect is:
{ 'outside: ': { value: {object_with_results}, done: false } }
{ 'inside: ': {object_with_results}
EDIT
I changed my code like that:
var getRelationsList = function *() {
var res = yield db.relations.find({});
console.log({'inside: ': res});
}
console.log({'outside ': co(getRelationsList)});
Now inside console log show's me good results but outside console log shows me just empty object.
Generators are a powerful tool for organizing asynchronous code, but they don't magically wait for asynchronous code to run.
What's Happening
Let's walk through your code so you can see what is happening:
getRelationsList is a generator function, that when called returns a new generator. At this point, no code in your generator function has been called (although if you were passing params they would be set). You then call .next on your generator to start execution of the generator function. It will execute up until it hits the first yield statement and return an object with the yielded value and the completion status of the generator.
It seems you understand most of that so far, but generators do not magically transform the yielded out values. When you yield out db.relations.find({}), you'll get the return value of the find function which I'm assuming is a Promise or some type of thenable:
so your 'outside' value is { value:Promise, done:false }
The reason your inside console.log never ran is that you're actually creating a new generator each time you call getRelationsList(), so when you call getRelationsList().next() again after the outside console.log you're creating a new generator and calling next, so it only executes up to the first yield, just like the call on the previous line.
In order to finish execution you must call next twice on the same instance of your generator: once to execute up to the yield and once to continue execution to the end of the function.
var gen = getRelationsList()
gen.next() // { value:Promise, done:false }
gen.next() // { value:undefined, done:true } (will also console.log inside)
You'll notice, however, if you run this, the inside console.log will be undefined. That's because the value of a yield statement is equal to the value passed to the following .next() call.
For example:
var gen2 = getRelationsList()
gen2.next() // { value:Promise, done:false }
gen2.next(100) // { value:undefined, done:true }
Outputs
{ inside:100 }
Because we passed 100 to the second .next() call and that became the value of the yield db.relations.find({}) statement which was then assigned to res.
Here's a link demoing all of this: http://jsfiddle.net/qj1aszub/2/
The Solution
The creators of koa use a little library called co which basically takes yielded out promises and waits for them to complete before passing the resolved value back into the generator function (using the .next() function) so that you can write your asynchronous code in a synchronous style.
co will return a promise, which will require you to call the .then method on to get the value returned from the generator function.
var co = require('co');
var getRelationsList = function *() {
var res = yield db.relations.find({});
console.log({'inside: ': res});
return res
}
co(getRelationsList).then(function(res) {
console.log({'outside: ': res })
}).catch(function(err){
console.log('something went wrong')
});
co also allows you to yield out other generator functions and wait for their completion, so you don't have to wrap things with co at every level and deal with promises until you're at some sort of 'top level':
co(function *() {
var list = yield getRelationsList()
, processed = yield processRelations(list)
, response = yield request.post('/some/api', { data:processed })
return reponse.data
}).then(function(data) {
console.log('got:', data)
})
Your problem is that you call getRelationsList() function multiple times which is incorrect.
Change your code to following
var g = getRelationsList();
console.log('outside: ', g.next());
g.next(); //You get your console.log('inside: .... here
Generators must be acted upon by outside code.
Under the hood koa use the co library to 'Run' the generator.
Here is how you might achieve what your wanting outside of koa:
var co = require('co');
var getRelationsList = function *() {
var res = yield db.relations.find({});
console.log({'inside: ': res});
}
co(getRelationsList).catch(function(err){});
I did a short screencast on JavaScript generators that should help you understand what's going on:
http://knowthen.com/episode-2-understanding-javascript-generators/
++ EDIT
If your using generators to program in more of an synchronous style (eliminating callbacks), then all your work needs to be done in the generator and you should use a library like co to execute the generator.
Here is a more detailed example of how you would interact with a generator, manually. This should help you understand the results your getting.
function * myGenerator () {
var a = yield 'some value';
return a;
}
var iterator = myGenerator();
// above line just instantiates the generator
console.log(iterator);
// empty object returned
// {}
var res1 = iterator.next();
// calling next() start the generator to either the
// first yield statement or to return.
console.log(res1);
// res1 is an object with 2 attributes
// { value: 'some value', done: false }
// value is whatever value was to the right of the first
// yield statment
// done is an indication that the generator hasn't run
// to completion... ie there is more to do
var toReturn = 'Yield returned: ' + res1.value;
var res2 = iterator.next(toReturn);
// calling next(toReturn) passes the value of
// the variable toReturn as the return of the yield
// so it's returned to the variable a in the generator
console.log(res2);
// res2 is an object with 2 attributes
// { value: 'Yield returned: some value', done: true }
// no further yield statements so the 'value' is whatever
// is returned by the generator.
// since the generator was run to completion
// done is returned as true
I've read over several examples of code using JavaScript generators such as this one. The simplest generator-using block I can come up with is something like:
function read(path) {
return function (done) {
fs.readFile(path, "file", done);
}
}
co(function *() {
console.log( yield read("file") );
})();
This does indeed print out the contents of file, but my hangup is where done is called. Seemingly, yield is syntactic sugar for wrapping what it returns to in a callback and assigning the result value appropriately (and at least in the case of co, throwing the error argument to the callback). Is my understanding of the syntax correct?
What does done look like when yield is used?
Seemingly, yield is syntactic sugar for wrapping what it returns to in a callback and assigning the result value appropriately (and at least in the case of co, throwing the error argument to the callback)
No, yield is no syntactic sugar. It's the core syntax element of generators. When that generator is instantiated, you can run it (by calling .next() on it), and that will return the value that was returned or yielded. When the generator was yielded, you can continue it later by calling .next() again. The arguments to next will be the value that the yield expresion returns inside the generator.
Only in case of co, those async callback things (and other things) are handled "appropriately" for what you would consider natural in an async control flow library.
What does done look like when yield is used?
The thread function example from the article that you read gives you a good impression of this:
function thread(fn) {
var gen = fn();
function next(err, res) {
var ret = gen.next(res);
if (ret.done) return;
ret.value(next);
}
next();
}
In your code, yield does yield the value of the expression read("file") from the generator when it is ran. This becomes the ret.val, the result of gen.next(). To this, the next function is passed - a callback that will continue the generator with the result that was passed to it. In your generator code, it looks as if the yield expression returned this value.
An "unrolled" version of what happens could be written like this:
function fn*() {
console.log( yield function (done) {
fs.readFile("filepath", "file", done);
} );
}
var gen = fn();
var ret1 = gen.next();
var callasync = ret1.value;
callasync(function next(err, res) {
var ret2 = gen.next(res); // this now does log the value
ret2.done; // true now
});
I posted a detailed explanation of how generators work here.
In a simplified form, your code might look like this without co (untested):
function workAsync(fileName)
{
// async logic
var worker = (function* () {
function read(path) {
return function (done) {
fs.readFile(path, "file", done);
}
}
console.log(yield read(fileName));
})();
// driver
function nextStep(err, result) {
try {
var item = err?
worker.throw(err):
worker.next(result);
if (item.done)
return;
item.value(nextStep);
}
catch(ex) {
console.log(ex.message);
return;
}
}
// first step
nextStep();
}
workAsync("file");
The driver part of workAsync asynchronously iterates through the generator object, by calling nextStep().