I have a Saga where I need to do 3 asynchronous requests, then use the responses from the 3 requests in a follow-up request. Here's some psuedo-code to explain:
function* useOtherActionsAndSagas(action) {
try {
const [response1, response2, response3] = yield [
request1,
request2,
request3
];
const orderData = {
...response1,
...response2,
...response3,
};
const response4 = yield request4;
yield put({ type: 'SUCCESS', data: response4 });
} catch (e) {
// handle error
}
The 3 requests request1
, request2
and request3
correspond to 3 separate Sagas. For example, for request1
there's a Saga along the lines of:
export function* request1(action) {
try {
const result = yield api.get(`/api/endpoint`);
yield put({...action, type: ACTION1_SUCCESS, data: result});
} catch (e) {
yield put({...action, type: ACTION1_FAIL, errors: e});
}
}
function* watchAction1() {
yield* takeLatest(ACTION1, request1);
}
export default function* () {
yield [fork(watchAction1)];
}
where api.get
is a wrapper for Axios.get().
This watcher in that Saga is connected to a corresponding action/reducer.
export const ACTION1 = "actions/ACTION1";
export const ACTION1_SUCCESS = "actions/ACTION1_SUCCESS";
export const ACTION1_FAIL = "actions/ACTION1_FAIL";
const initialState = {
// Initial state
};
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case ACTION1:
// return state
case ACTION1_SUCCESS:
// return state
case ACTION1_FAIL:
// return state
};
default:
// return state;
}
}
export function request1(data) { return {type: ACTION1, data}; }
To keep my code DRY I was hoping to take advantage of the existing action and saga in the parent saga. To do this I tried:
const [response1, response2, response3] = yield [
put({type: ACTION1, data: data1}),
put({type: ACTION2, data: data2}),
put({type: ACTION3, data: data3})
];
This correctly initiates each action and their corresponding sagas. However, the response from the requests are not available in the assigned variables. That is, response1
, response2
and response3
are references to their actions {type: "actions/ACTION1", data: data1}
and not a Promise.
I know it would be possible to duplicate the Axios requests in this parent Saga but I'd lose the bonus of having the success/fail actions for the individual requests.
Is it possible to use a setup like this? If so, how can the responses from the asynchronous requests be retrieved for use in a follow-up request?
If not, what is the correct method for accomplishing this?
I can use the workers from the other Sagas within the parent saga, like this:
import request1 from request1Saga;
const [response1, response2, response3] = yield [
call(request1, data1),
call(request2, data2),
call(request3, data3),
];
where request1
, request2
and request3
are the worker functions from other Sagas. That gives the benefit of the ACTION1_SUCCESS
and ACTION1_FAIL
actions from those Sagas being used.
Calling a saga from another saga is as simply as doing a call effect.
Since you're using takeEvery , no, there's no way to dispatch a GET_USER action from within your saga without triggering an infinite loop.
Such a powerful & elegant tool as Redux-Saga, a Redux side effect manager, is said to be deprecated, and no longer being maintained, starting from Jan 27, 2021.
Create a plain JavaScript Object to instruct the middleware that we need to dispatch some action, and let the middleware perform the real dispatch. This way we can test the Generator's dispatch in the same way: by inspecting the yielded Effect and making sure it contains the correct instructions.
All you need is combine all
combinator with call
effect (docs for composing sagas and running tasks in parallel):
const [response1, response2, response3] = yield all([
call(request1),
call(request2),
call(request3)
]);
This will execute sagas in parallel and return results from each of them. It works as Promise.all
.
The sagas above (request1 to request3) need to return some data at the end of saga:
export function* request1(action) {
try {
const result = yield call(url => api.get(url), `/api/endpoint`);
yield put({...action, type: ACTION1_SUCCESS, data: result});
// This will be assigned to result1
return result
} catch (e) {
yield put({...action, type: ACTION1_FAIL, errors: e});
}
}
Note: You don't need to fork takeEvery, because it is already "forked":
// Example of root saga:
export default function* () {
yield takeLatest(ACTION1, request1);
yield takeLatest(ACTION2, request2);
// ...
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With