I have a bunch of action watchers of my api calls using redux-saga. The thing is I would like to make ONE action watcher which fire all these action watchers to fetch all the api without having to repeat the codes I already have. If one of the watcher return a rejected Promise, it should cancel all the other watchers. What's the best way of doing this?
function* watchFetchUsers() {
while(true) {
yield take([FETCH_USERS]);
try {
const users = yield call(api.fetchUserData);
yield put({ type:FETCH_USERS, payload: users });
} catch (err) {
yield put({ type:SIGNOUT, status: err });
return err;
}
}
}
function* watchFetchDepartments() {
while(true) {
yield take([FETCH_DEPARTMENTS]);
try {
const departments = yield call(api.fetchDepartmentData);
yield put({ type:FETCH_DEPARTMENTS, payload: departments });
} catch (err) {
yield put({ type:SIGNOUT, status: err });
return err;
}
}
}
function* watchFetchPositions() {
while(true) {
yield take([FETCH_POSITIONS]);
try {
const positions = yield call(api.fetchPositionData);
yield put({ type:FETCH_POSITIONS, payload: positions });
} catch (err) {
yield put({ type:SIGNOUT, status: err });
return err;
}
}
}
function* watchFetchBanks() {
while(true) {
yield take([FETCH_BANKS]);
try {
const banks = yield call(api.fetchBankData);
yield put({ type:FETCH_BANKS, payload: banks });
} catch (err) {
yield put({ type:SIGNOUT, status: err });
return err;
}
}
}
function* watchFetchAuthenticatedUser() {
while(true) {
yield take([FETCH_AUTHENTICATED_USER]);
try {
const user = yield call(api.fetchAuthenticatedUser);
yield put({ type:FETCH_AUTHENTICATED_USER, payload: user });
} catch (err) {
yield put({ type:SIGNOUT, status: err });
return err;
}
}
}
export default function* fetchData() {
yield [
fork(watchFetchUsers),
fork(watchFetchDepartments),
fork(watchFetchPositions),
fork(watchFetchBanks),
fork(watchFetchAuthenticatedUser)
];
}
How about this
export function* watchFetchAll() {
while(true) {
// const {type} = yield take(['FETCH_A', 'FETCH_B', ...]);
const {type} = yield take(action => /^FETCH_/.test(action.type));
console.log('type %s', type);
try {
const data = yield call(api.fetch, type);
console.log('data', data);
yield put({type, payload: data})
}
catch (error) {
console.log('error', error);
yield put({ type: 'SIGNOUT', status: error })
}
}
}
export default function* fetchData() {
yield *watchFetchAll();
}
The simple api implementation:
const api = {
fetch(type) {
switch (type) {
case 'FETCH_A': return Promise.resolve({result: 'Fetched A type'});
case 'FETCH_B': return Promise.resolve({result: 'Fetched B type'});
// other cases
default: console.log(`Unknown type ${type}`);
}
}
};
The forked task's error is propagated to parent tasks.
I'm not sure if the below is what you want. But maybe it will work.
function* watchFetchUsers() {
while(true) {
yield take([FETCH_USERS]);
const users = yield call(api.fetchUserData);
yield put({ type:FETCH_USERS, payload: users });
}
}
function* watchFetchDepartments() {
while(true) {
yield take([FETCH_DEPARTMENTS]);
const departments = yield call(api.fetchDepartmentData);
yield put({ type:FETCH_DEPARTMENTS, payload: departments });
}
}
function* watchFetchPositions() {
while(true) {
yield take([FETCH_POSITIONS]);
const positions = yield call(api.fetchPositionData);
yield put({ type:FETCH_POSITIONS, payload: positions });
}
}
function* watchFetchBanks() {
while(true) {
yield take([FETCH_BANKS]);
const banks = yield call(api.fetchBankData);
yield put({ type:FETCH_BANKS, payload: banks });
}
}
function* watchFetchAuthenticatedUser() {
while(true) {
yield take([FETCH_AUTHENTICATED_USER]);
const user = yield call(api.fetchAuthenticatedUser);
yield put({ type:FETCH_AUTHENTICATED_USER, payload: user });
}
}
export default function* fetchData() {
while (true) {
let tasks;
try {
tasks = yield [
fork(watchFetchUsers),
fork(watchFetchDepartments),
fork(watchFetchPositions),
fork(watchFetchBanks),
fork(watchFetchAuthenticatedUser)
];
yield join(...tasks)
} catch (e) {
yield cancel(...tasks);
yield put({ type:SIGNOUT, status: err });
}
}
}
Or if you don't want to restore tasks,
//....
export default function* fetchData() {
try {
yield [
fork(watchFetchUsers),
fork(watchFetchDepartments),
fork(watchFetchPositions),
fork(watchFetchBanks),
fork(watchFetchAuthenticatedUser)
];
} catch (e) {
yield put({ type:SIGNOUT, status: err });
}
}
The answer called Parallel Task wherein you call 2 or more api at the same time, also efficient to handle than to this (example below)
// wrong, effects will be executed in sequence
const users = yield call(fetch, '/users')
const repos = yield call(fetch, '/repos')
//instead, try this one
import { all, call } from 'redux-saga/effects'
const [users, repos] = yield all([
call(fetch, '/users'),
call(fetch, '/repos')
])
for more information regards with your issue, I prefer to read more about sync and async of redux saga, Redux-Saga Parallel task and Non-Blocking Call
Related
function* imageUploadfunctionCall(payload) {
for (let image of payload.payload) {
const {response, error} = yield call(imageUploadRequest(image))
if (response) {
yield put({type: ON_UPLOAD_SUCCESS, payload: image})
} else if (error) {
console.log('error', error)
}
}
}
export function* watchImageUpload() {
while (true) {
let workerTask = yield takeEvery(
ON_UPLOAD_PROGRESS,
imageUploadfunctionCall
)
yield take(ON_CANCEL_BATCH_UPLOAD)
yield cancel(workerTask)
}
}
There is multiple ways you can do this, for example you can use an in-between saga with a race effect:
function* imageUploadfunctionCall(payload) {
for (let image of payload.payload) {
const {response, error} = yield call(imageUploadRequest(image))
if (response) {
yield put({type: ON_UPLOAD_SUCCESS, payload: image})
} else if (error) {
console.log('error', error)
}
}
}
function* imageUploadSaga(payload) {
yield race([
call(imageUploadfunctionCall, payload),
take(a => a.type === ON_CANCEL_BATCH_UPLOAD && a.id === payload.id),
])
}
export function* watchImageUpload() {
yield takeEvery(ON_UPLOAD_PROGRESS, imageUploadSaga)
}
The code above assumes that you send an id property for both the ON_UPLOAD_PROGRESS & ON_CANCEL_BATCH_UPLOAD actions so you can identify which one to cancel.
On a side note, in the upload saga you have:
yield call(imageUploadRequest(image))
which should be probably instead
yield call(imageUploadRequest, image)
(unless imageUploadRequest is a function factory).
For more complex cases you could hold a map of tasks & ids.
export function* watchImageUpload() {
const taskMap = {}
yield takeEvery(ON_CANCEL_BATCH_UPLOAD, function* (action) {
if (!taskMap[action.payload]) return
yield cancel(taskMap[action.payload])
delete taskMap[action.payload]
})
while (true) {
let payload = yield take(ON_UPLOAD_PROGRESS, imageUploadSaga)
const workerTask = yield fork(imageUploadfunctionCall, payload)
taskMap[payload.id] = workerTask
}
}
Again, you need some id in both actions.
How can i catch error? when i use redux-saga?
if error occred in const refreshresult = yield call(refresh);
i want to stop and throw this error action
i don't know what can i do
yield put({
type: REFRESH_FAILURE,
error: err.response.data,
});
how can i do that? how should i stop and send error action?
this is my code
function getPostAPI(data) {
return axiosInstace.post('/kakao/getpost', data);
}
function* getPost(action) {
try {
const result = yield call(getPostAPI, action.data);
yield put({
type: GETPOST_SUCCESS,
data: result.data,
});
} catch (err) {
if (err.response.data === 'jwtEx') {
const refreshresult = yield call(refresh); // <<<< if error occure refresh() i want to stop
yield put(action);
} else {
yield put({
type: GETPOST_FAILURE,
error: err.response.data,
});
}
}
}
function* refresh() {
try {
const result = yield call(refreshAPI);
yield AsyncStorage.setItem(
'accesstoken',
`${result.data.accessToken}`,
() => {
// console.log('accesstoken 재발급 저장 완료');
},
);
yield put({
type: REFRESH_SUCCESS,
data: result.data,
});
} catch (err) {
yield put({
type: REFRESH_FAILURE,
error: err.response.data,
});
}
}
I guess you're doing it the right way. Try and catch will work.
Check out this link for more info Redux-saga documentation
How to stop saga being called multiple times. Once i dispatched an action i received the result multiple times. i don't know what i'm missing here.
Saga.js
export function* watchRegisterUser() {
yield takeLatest(REGISTER_USER, registerWithEmailPassword);
}
export function* watchLoginUser() {
const logi = yield takeLatest(LOGIN_USER, loginWithEmailPassword);
}
export function* watchLogoutUser() {
yield takeLatest(LOGOUT_USER, logout);
}
export default function* rootSaga() {
yield all([
fork(watchLoginUser),
fork(watchLogoutUser),
fork(watchRegisterUser)
]);
}
When i put wrong credentials i get the response and notification is displayed, working fine for the first time. But when i change the credentials, i get notifications (multiple times). Even when state
Reference: Same problem here
I'm not sure what am i missing here.
Thanks
Update
function* loginWithEmailPassword({ payload }) {
const { history } = payload;
try {
const response = yield call(loginWithEmailPasswordAsync, payload.user);
console.log("login :", response);
if (response.status >= 200 && response.status < 300) {
const loginUser = yield response.json();
localStorage.setItem("access_token", loginUser.access_token);
yield put(loginUserSuccess(loginUser));
history.push("/");
} else if (response.status === 400) {
yield put({
type: LOGIN_USER_FAILURE,
error: "Email and Password are wrong!"
});
}
} catch (error) {
yield put({ type: LOGIN_USER_FAILURE, error: "Something went wrong!" });
}
}
you could try debounce to get around this, in the example below I debounce repeated actions withing a 500ms window.
function* createAutocompleteLists(action) {
const state = yield select()
if (!state.users.loaded && !state.users.loading) {
yield put(getUsers())
yield take([GET_SUCCESS, GET_FAIL])
}
yield call(parseAutocomplete, state.couchdbSavedSearch.current_share_with, state)
}
function* createAutocompleteForwardLists(action) {
const state = yield select()
if (!state.users.loaded && !state.users.loading) {
yield put(getUsers())
yield take([GET_SUCCESS, GET_FAIL])
}
yield call(parseForwardAutocomplete, state.forward.current_forward_to, state)
}
function* debounceCurrentShareWith () {
yield debounce(500, [GET_SUCCESS, SET_CURRENT_SHARE_WITH, SPACE_SET_CURRENT_SHARE_WITH], createAutocompleteLists)
}
function* debounceCurrentShareWithForward () {
yield debounce(500, [GET_SUCCESS, FORWARD_SET_CURRENT_FORWARD_TO], createAutocompleteForwardLists)
}
export default function* usersSagas() {
yield spawn(debounceCurrentShareWith)
yield spawn(debounceCurrentShareWithForward)
}
Here is store/store.js
...
const initSagaMiddleware = createSagaMiddleware();
const middlewares = [initSagaMiddleware, fetchPhotosMiddleware, putPhotoMiddleware];
const middlewareEnhancer = applyMiddleware(...middlewares);
...
initSagaMiddleware.run(rootSaga);
export default store;
sagas/api-saga.js
export default function* rootSaga() {
yield all([
photoWatcher()
]);
}
function* photoWatcher() {
yield takeEvery(PUT_PHOTO, putPhotoWorker);
}
function* putPhotoWorker(action) {
try {
const payload = yield call(putPhoto, action.urlParams, action.body);
yield put({ type: PHOTO_UPDATED, payload });
} catch (err) {
yield put({ type: API_ERROR_PUT_PHOTO, payload: err });
}
}
and services/api.js
export function putPhoto(urlParams, body) {
return axios.put('/abc/' + urlParams.key + '/def/' + urlParams.id + '/edit', body)
.then(res => {
return res.data;
})
.catch(err => err);
}
My problem is: even when I get error from an api call, redux-saga putting PHOTO_UPDATED instead of API_ERROR_PUT_PHOTO. What am I doing wrong? How to catch error?
The problem is you are trying to catch the same error twice. Just remove the catch form the putPhoto function and it should work.
export function putPhoto(urlParams, body) {
return axios.put('/abc/' + urlParams.key + '/def/' + urlParams.id + '/edit', body)
.then(res => {
return res.data;
});
}
Both then and catch promises in putPhoto func were resulting in try statement. Because of that, I give up on try...catch, instead I'm using if...else statement now. Like this:
function* putPhotoWorker(action) {
const payload = yield call(putPhoto, action.urlParams, action.body);
if (isResponseTypeWhatIExpected) {
yield put({ type: PHOTO_UPDATED, payload });
} else {
yield put({ type: API_ERROR_PUT_PHOTO, payload });
}
}
I see many questions about this but nothing from answers helps me to solve it. I wanted to see how saga works since I haven't worked with it.
Here is the code
export function* fetchItems() {
try {
yield put({ type: ITEMS_FETCH_REQUEST })
const response = yield call(fetch, 'https://jsonplaceholder.typicode.com/users');
const data = response.json();
yield put({ type: ITEMS_FETCH_SUCCESS, payload: { items: data } })
} catch (error) {
yield put({ type: ITEMS_FETCH_FAILURE, payload: { error: error.message }})
}
}
It calls an infinite loop, I tried a lot of things but nothing helps.
What am I doing wrong here ?
I figured it out, for watchAsyncSagaFunction generator function you need to create another constant that is different than the one that you call at the beginning of asyncSagaFunction.
Example:
export function* asyncSagaFunction() {
try {
yield put({ type: ITEMS_FETCH_REQUEST })
const response = yield call(fetch, 'https://jsonplaceholder.typicode.com/users');
const data = response.json();
yield put({ type: ITEMS_FETCH_SUCCESS, payload: { items: data } })
} catch (error) {
yield put({ type: ITEMS_FETCH_FAILURE, payload: { error: error.message }})
}
}
There is ITEMS_FETCH_REQUEST, you need to create another one, for example,ITEMS_FETCH and in a component call that one.
export function* watchAsyncSagaFunction() {
yield takeLatest(ITEMS_FETCH, fetchItems)
}