Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Redux-Saga: Is there a guaranteed order of saga execution?

If i have two sagas waiting for a yield take() on the same action, is there a guarantee which saga will pick up the action first and execute its remaining logic or is random? I need to ensure the first saga executes its logic before the second saga does.

function* rootSaga() {
    yield [ 
        saga1(),
        saga2()
    ]
}

function* saga1() {
    while (true) {
        //Random logic
        yield take("MY_ACTION")
        //Finish executing logic
    }
}

function* saga2() {
    while (true) {
        //Random logic
        yield take("MY_ACTION")
        //Finish executing logic
    }
}
like image 248
Gabriel West Avatar asked Oct 28 '17 19:10

Gabriel West


2 Answers

You cannot rely here on the order of execution. When your action is dispatched, all sagas that have yielded a take effect matching the action will be resumed at once. If an order of execution was "guaranteed", it would be an implementation detail you shouldn't rely on.

If you need your saga2 to be resumed after saga1 has executed the logic subsequent to "MY_ACTION", what your saga2 should really be waiting for is a different action indicating that saga1 has finished its job, rather than the first one.

function* rootSaga() {
  yield [ 
    saga1(),
    saga2()
  ]
}

function* saga1() {
  while (true) {
    //Random logic
    yield take("MY_ACTION")
    //Finish executing logic
    yield put({type: "MY_ACTION_DONE"})
  }
}

function* saga2() {
  while (true) {
    //Random logic
    yield take("MY_ACTION_DONE")
    //Finish executing logic
  }
}
like image 186
VonD Avatar answered Nov 14 '22 23:11

VonD


Rather than having both run independently, you can call or fork the dependent generator from the initial one after the necessary logic has been performed.

Note there is a subtle but important difference between call and fork. call(saga2) is blocking so would pause your while loop and not react to any more "MY_ACTION" actions until saga2 has completed too, whereas fork(saga2) is non-blocking, acting like a background task so would continue to execute in parallel to your loop resuming, so you could continue to respond to further "MY_ACTION".

Another thing to note is that fork is still attached to the parent saga, so will be cancelled along with it, errors will bubble to parent from it etc. I imagine this is desirable but if you need a completely detached version that will continue to run no matter what happens to parent saga, use spawn instead

function* rootSaga() {
    yield all([ 
        fork(saga1)
    ])
}

function* saga1() {
    while (true) {
        //Random logic
        const action = yield take("MY_ACTION")
        //Finish executing logic

        // BLOCKING `call` saga2, passing original action as param if needed
        yield call(saga2, action)

        // OR NON-BLOCKING `fork` saga2
        yield fork(saga2, action)

        // OR NON-BLOCKING DETACHED `spawn` saga2
        yield spawn(saga2, action)
    }
}

function* saga2(action) {
    // Do whatever dependant logic
    // safe in the knowledge that saga1 has done its job already
}
like image 28
alechill Avatar answered Nov 14 '22 22:11

alechill