Using Action/Saga in another Saga
Asked Answered
G

1

11

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?

Update

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.

Gibe answered 12/6, 2017 at 11:37 Comment(8)
Which version of redux-saga are you using? I think from v0.15.0 they use yield all([]) now Have a look at - github.com/redux-saga/redux-saga/blob/…Atworth
Currently on 0.12.1, I can update but I don't think that will fix the issue of the responses.Gibe
Ya I dont think it will make any difference. One more thing though isn't yield [] looking for promise object? here you have just state return though.Atworth
Yes, that's the crux of the issue I think. I want to reuse the external action/saga to take advantage of their success/fail actions, as well as not duplicate the existing request already in another saga.Gibe
Did you think of fork model for reusing sagas? any downside with that?Atworth
I haven't. How do you see that working? I'm making some progress, by having my Saga worker from the sub-sagas return the Promise, and call(subSagaWorker) within the array of requests.Gibe
I am not sure I follow your need properly. All I understand is that there are three sagas and you don't want to loose your error handling logic. If so this pattern should do the trick -redux-saga.js.org/docs/advanced/ForkModel.html. But here one saga fails your fork will enter catch block immediately without waiting for other requests.Atworth
I suppose that will work if I passed the Saga worker from the sub-sagas to each fork(). I'm doing something similar with the array of requests now.Gibe
B
3

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);
  // ...
}
Bismuth answered 13/6, 2017 at 10:7 Comment(2)
In this solution (that the OP seemed to be happy with) you are running the request# sagas and such get the ACTION#_SUCCESS/FAILURE dispatched, but the ACTION# is not run and thus the reducer for ACTION# is not used. What would you need to do if you wanted the ACTION# reducers to also run?Kinnard
@Kinnard This saga was triggered by listening for ACTION1, and actions only get passed to redux-saga AFTER they have run through redux reducer first. So here you are always guaranteed that any reduction needed to be done using ACTION1 has already completed by the time you process further side-effects in the sagaChlamydate

© 2022 - 2024 — McMap. All rights reserved.