Run sagas in sequence - javascript

I am trying to write some code that will execute several blocking sagas in sequence. For my use case, saga1 must complete before saga2 can start executing. Here is some code that shows a simplified version of what I am trying to do:
function* logger() {
console.log('spy 1');
}
function* logger2() {
console.log('spy2');
}
function* spy1() {
yield takeEvery('*', logger);
}
function* spy2() {
yield takeEvery('*', logger2);
}
export default function* rootSaga() {
yield call(spy1);
yield call(spy2);
}
When I dispatch an action, I only ever reach the first console.log. I know that if I use fork() instead of call() I can get both to run, however I do not want them to run in parallel. How can I make my first logger function complete and allow saga to move on to the second.
Thanks!

What about creating a single watcher that simulates what takeEvery does but in a sequencial manner?
function* logger() {
yield delay(1000);
console.log("logger 1");
}
function* logger2() {
console.log("logger 2");
}
function* watcher() {
while (yield take("*")) {
yield call(logger);
yield call(logger2);
}
}
function* rootSaga() {
yield call(watcher);
}
One of the points I'd like to bring here is that, first is that takeEvery uses a fork internally and then according to takeEvery's documentation:
There is no guarantee that the tasks will terminate in the same order they were started
so maybe using takeEvery won't accomplish what you need.

Related

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.

How do I make ES6 generators wait for promises, like in redux-saga?

I've read that generators don't wait for promises. How come this is not the case with generators in redux-saga, and how do I make my own generators wait?
For example, this saga:
takeLatest('FETCH_USER_REQUESTED', function*() {
const fetchPromise = yield put(fetchUser());
const user = yield fetchPromise;
console.log(user)
yield 1
console.log(1)
})
will output:
Promise
Object // <= user data fetched asynchronously
1
instead of:
Promise
undefined
1
How come this is not the case with generators in redux-saga, and how do I make my own generators wait?
This very popular belief, however generators in itself have no relation to Promises or asynchronous functions. Generators is just about make interruptible function with delegating some resources and responsibility to upper level function.
In case of redux-saga, there is two parts: independent saga runner process and scheduler (https://github.com/redux-saga/redux-saga/blob/master/src/internal/runSaga.js) , which is launched by sagaMiddleware.run() command, and effects reactions, which delegates actions into main saga process.
So, simplest process manager in ES6, which emulates redux-saga behavior, will be like that (very simplified):
const ProcessManager = (() => {
let context = new WeakMap();
function PM(rootSaga, lastValue) {
if(!context.has(rootSaga)) {
context.set(rootSaga, rootSaga())
}
const iterator = context.get(rootSaga);
const { done, value } = iterator.next(lastValue);
if(done) {
context.delete(rootSaga)
return;
}
if(Promise.resolve(value) === value) {
value.then((asyncValue) => PM(rootSaga, asyncValue))
} else {
PM(rootSaga, value)
}
}
return PM;
})()
const rootSaga = function* () {
yield new Promise(resolve => setTimeout(resolve, 500));
console.log('This will be printed after 500 ms from start');
yield new Promise(resolve => setTimeout(resolve, 500));
console.log('This will be printed after 1000 ms from start');
}
ProcessManager(rootSaga);

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();

Understanding code flow with yield/generators

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

Categories

Resources