I try to call another function after yield all([])
When I replace call() effect with fork(), it works. I know that fork() is no-blocking effect. But assuming that all the effects are resolved (using call()), next line should be also called.
After all() effect I try to call another generator with WebSockets. Maybe it is not the best place, but I don't know why it doesn't work anyway.
export function* someGenerator () {
try {
return true
} catch (e) {}
}
export function* watchSomeAction() {
yield takeEvery('someAction', someGenerator )
}
export default function* rootSaga () {
yield all([call(watchSomeAction)])
yield call(anotherGenerator) \\ never called even when all the effects are
resolved
}```
Related
I've always assumed that takeEvery (or takeLatest etc.) is blocking and will live as long as its parent lives due to it using a while(true) in its implementation (or basic implementation).
However, i've put a basic example together that demonstrates the saga running to the finally block immediately after calling takeEvery.
import { call, takeEvery } from "redux-saga/effects";
function* handleTest(data: any) {
yield call([console, console.log], data);
}
export function* rootSaga() {
try {
yield takeEvery("test", handleTest);
} finally {
console.log("end");
}
}
https://codesandbox.io/s/pusher-transcriptions-ff7qz?file=/src/rootSaga.ts:0-259
According to the official docs it is not blocking https://redux-saga.js.org/docs/api/#blocking--non-blocking
I have encountered the following scenario when trying to test a saga from redux saga.
Some package information:
I use redux-saga#0.15.6, redux#3.7.2 with node v9.5.0 and npm 5.6.0.
I have the following structure:
sagas
index.js
index.spec.js
Inside index.js, I define my sagas. A basic idea of what I have there is this:
function doSomething() {
/* Some code */
}
function* notExportedSaga() {
yield takeLatest(SOME_ACTION, doSomething)
}
export function* exportedSaga(action) {
yield put({ type: OTHER_ACTION, payload: somePayload })
}
In index.spec.js, I want to test my sagas. I have a import at the top of my file:
import { exportedSaga } from './index'
With the structure and code I described, this works just fine. However, if I change doSomething from being defined like it is to a fat arrow function:
const doSomething = () => {
/* Some code */
}
What will happen is, when running the unit tests, I will get this error:
console.error node_modules/redux-saga/lib/internal/utils.js:240
uncaught at rootSaga
at rootSaga
at rootSaga
ReferenceError: doSomething is not defined
Do you know why this is happening?
I have open a issue , because I don't know if this is a bug or not.
Function declarations like
function doSomething() { }
are being hoisted to the top
Function expressions like
const doSomething = () => { }
are not being hoisted
That's why you're getting doSomething is not defined - both notExportedSaga and exportedSaga functions are being hoisted to the top while the expression function const doSomething = () => { } is not and it is undefined on your generator functions invocations.
Here's a cool article about hoisting if you want to know more :) https://scotch.io/tutorials/understanding-hoisting-in-javascript
I have whole application depending on config data that are loaded with request from server, how can I create blocking "before" action on every action using redux saga, now my globalSaga looks like this. The help would be really appreciated
function * rootSaga () {
yield takeLatest(LOAD_ONBOARDING.REQUEST,loadOnboardingSaga)
const res = yield put.resolve(loadOnboarding())
yield console.log(res)
yield all([
fork(globalSaga),
fork(spaceSaga),
fork(profileSaga),
fork(userSaga),
fork(pagesSaga)
])
}
takeLatest is the same as forking with automatic cancellation of each subsequent fork. Check this for a descriptive example.
So in your case since loadOnboardingSaga should continuously block next calls the cheapest solution would be to put it all under loadOnboardinSaga since there is a direct succession like this:
function* initRestSaga() {
yield all([
fork(globalSaga),
fork(spaceSaga),
fork(profileSaga),
fork(userSaga),
fork(pagesSaga)
])
}
function* loadOnboardingSaga() {
//...
const res = yield put.resolve(loadOnboarding())
yield console.log(res)
yield call(initRestSaga)
}
function* rootSaga() {
yield takeLatest(LOAD_ONBOARDING.REQUEST, loadOnboardingSaga)
}
Otherwise you have to manually fork and cancel each and every side effect in between takeLatest and last fork.
I'm a noob in redux-sagas, so please bear with me if I miss out something obvious!
I have a scenario wherein I'm using redux-saga to fetch an access_token from an OIDC provider and store it in the browser's localStorage.
I also use Sagas to fetch some data from an API endpoint.
But I've been experiencing problems with this approach because the Saga calling the external API gets invoked before the Auth Saga can resolve with an access_token.
My Auth Saga :
export function * handleFetchTokens () {
try {
const token = yield cps(getToken)
localStorage.setItem('token', token)
const isAuthenticated = !!(token)
yield put(actions.checkAuthSuccess(isAuthenticated))
} catch (e) {
yield put(actions.checkAuthFailure(e))
}
}
export default function * sagas () {
yield fork(takeLatest, actions.CHECK_AUTH, handleFetchTokens)
}
My API Saga :
export function * handleFetchItems () {
try {
const response = yield call(getItems)
yield put(actions.fetchItemsSuccess(response))
} catch (e) {
yield put(actions.fetchItemsFailure(e.errors))
}
}
export default function * sagas () {
yield fork(takeLatest, actions.FETCH_ITEMS, handleFetchItems)
}
My root Saga :
export default function * root () {
yield fork(items.sagas)
yield fork(authentication.sagas)
}
What should be the proper way of overcoming this problem?
Personally, I'd make sure the token is received before allowing any part of my app to actually call the FETCH_ITEMS action. Assuming you don't want to introduce such logic you will have to decide what to do with FETCH_ITEMS actions before you get the token.
The easiest approach would be to just ignore them, but that also probably isn't the most feasible way to go.
So what remains is to buffer the FETCH_ITEMS actions. You can do this using actionChannel. Since you are using takeLatest you will also want to define a sliding buffer of size 1.
It could look ruffly like this:
export default function * sagas () {
const chan = yield actionChannel(actions.FETCH_ITEMS, buffers.sliding(1))
yield take('TOKEN_RECEIVED') // wait for action informing us token was received
chan.close()
yield fork(takeLatest, chan, handleFetchItems)
yield fork(takeLatest, actions.FETCH_ITEMS, handleFetchItems)
}
More about actionChannels here https://redux-saga.js.org/docs/advanced/Channels.html
Another approach - with a bit less writing but also a bit less control - instead of buffering is to wait in the fetching saga itself:
export function * handleFetchItems () {
while (!token) yield take('TOKEN_RECEIVED');
...
}
Either way, both ways rely on waiting for a TOKEN_RECEIVED action you need to dispatch once the token is received.
It tells me "promise is not a function". My problem is that with isomorphic fetch you have to put twice then to get your parsed result. What should I do to manage that properly with redux-saga generators ?
import { put, call, takeEvery, takeLatest } from 'redux-saga/effects'
import fetch from 'isomorphic-fetch'
import errorMessages from './../conf/errorMessages'
function *fetchBalances(address) {
try {
var request = fetch('/api/getBalances/rJnZ4YHCUsHvQu7R6mZohevKJDHFzVD6Zr').then(function(response) {
return response.json();
}). then(function(result) {
// finally my parsed result !
return result;
});
const balances = yield call(request)
yield put({ type: 'GET_BALANCES_SUCCEED', balances: balances})
}
catch(error) {
yield put({ type: 'GET_BALANCES_ERROR', error: error })
}
}
export function* watchGetBalances() {
yield takeEvery('GET_BALANCES', fetchBalances);
}
I could put that in a closure but is that the best idea ? =/
var request = function() {
return fetch('/api/getBalances/rJnZ4YHCUsHvQu7R6mZohevKJDHFzVD6Zr').then(function(response) {
return response.json();
}). then(function(result) {
return result;
});
}
The confusion here comes from the fact that the request variable that you're passing to the call effect holds a Promise. What you want, is the call effect to execute that Promise for you.
In other words, you have already manually called the fetch function, instead of passing call a function that will call and return it.
So, yes, wrapping your fetch calls in a, eg. callRequest function could be useful but if you do so, you must be careful to write const response = yield call(callRequest), and not const response = yield call(callRequest()), which would be equivalent to what you wrote.
A few precisions, in case it helps. From the docs, call receives as its first argument:
A Generator function, or normal function which either returns a Promise as result, or any other value.
Let's see how that works.
First a generator function.
const response = yield call(myFunction);
function* myFunction() {
yield delay(1000);
return true;
}
// response === true
Then a function returning some value (not the most useful, but…)
const response = yield call(myFunction);
function myFunction() {
return true;
}
// response === true;
Finally a function returning a Promise
const response = yield call(callRequest);
function callRequest() {
return fetch(…).then( r => r.json() )
}
// response holds your data
// if the Promise were to be rejected, you could catch
// the error in a try-catch block