Misunderstandings About put Operations in Redux-Saga

· 2 min read

Regarding effects in saga, our normal usage seems to work fine. But yesterday during a code review, a question came up - is put action asynchronous? Such a simple question, but I couldn’t give an absolutely correct answer. True or False, I wasn’t sure. So, by reading saga source code, official documentation and testing with demos, I’ll provide an accurate answer while deepening understanding of saga.

Let me start with the conclusion: it depends. If the action has no middleware processing or async blocking, then it’s synchronous. If it does, then it’s asynchronous.

WHY? Let me elaborate!

What is an effect

effect refers to side effect. Side effect is a concept in computer science, not original to Saga.

What is a side effect? In computer science, a function side effect refers to when calling a function, in addition to returning the function value, it also has additional impact on the calling function. For example, modifying global variables (variables outside the function), modifying parameters, or changing external storage.

概念摘自维基百科,戳这里

Saga categorizes effects into many types, as follows:

export const effectTypes: {
  TAKE: 'TAKE'
  PUT: 'PUT'
  ALL: 'ALL‘
  RACE: 'RACE'
  CALL: 'CALL'
  CPS: 'CPS'
  FORK: 'FORK'
  JOIN: 'JOIN'
  CANCEL: 'CANCEL'
  SELECT: 'SELECT'
  ACTION_CHANNEL: 'ACTION_CHANNEL'
  CANCELLED: 'CANCELLED'
  FLUSH: 'FLUSH'
  GET_CONTEXT: 'GET_CONTEXT'
  SET_CONTEXT: 'SET_CONTEXT'
}

So, when we use Call or Put in daily development, we’re triggering different effects.

What are Blocking and Non-blocking Operations

Saga has this pair of concepts. What do they mean?

A Blocking call means that the Saga yielded an Effect and will wait for the outcome of its execution before resuming to the next instruction inside the yielding Generator.

A Non-blocking call means that the Saga will resume immediately after yielding the Effect.

意思就是阻塞调用将会等其执行完最终输出,而非阻塞的将会在effect call后恢复继续执行

Official explanation, click here

Is put Operation Blocking or Non-blocking?

In effects, to dispatch an action, we use the put function, and put is non-blocking. The blocking version is putResolve.

What Other Operations Exist Besides put?

  1. Check the official website
  2. Look at the source code

I won’t go into detail here.

Which Effect Functions are Blocking and Which are Non-blocking

By examining the saga source code, I’ve organized this [call me Lei Feng]

functionblock
takeBlocking
callBlocking
allBlocking
putNon-Blocking
putResolveBlocking
forkNon-blocking
cancelNon-blocking
joinBlocking
cpsNon-blocking

Misunderstanding About Non-blocking Operations

After reading the above introduction, it seems we might have a mature understanding - is putting an action asynchronous?

Example

Assume user information is initialized as null

initState

{
    'name': null,
    'age': null
}

effects

function* fetchUserEffects() {
    const user = (yield call(getUserInfo)).data; // {"name": "alan","age": 29}
    yield put(setUserInfo(user));
    console.log('Class: fetchUserEffects, Function: fetchUserEffects, Line 8 yield select(): ', yield select(state => state.user));
}

reducers

 case 'USER_FETCH_SUCCEEDED':
      console.log('Class: , Function: user, Line 7 (): ', action.user);
      return {
      ...action.user
      };

As above, if it were asynchronous, it seems the user information printed in effects should be empty, however…

The result is that user information is not empty, which means after yielding put an action, the reducer executes completely and updates the store before continuing with the next operation.

The previous understanding of asynchronous was wrong. So what does non-blocking really mean? I checked GitHub and found someone had the same question. After reading it again and again, I finally understood.

  1. For the above example, put dispatches an action, which is a synchronous operation. So the next store information retrieved will definitely be the latest.
  2. Non-blocking means that if this action has middleware or some asynchronous operations that cause store information updates to be delayed, the effects won’t wait for these operations to complete - it will continue executing the next operations.

Modifying Action to be Asynchronous

We introduce thunk. For specific configuration, check the official website.

thunks

export const fetchUser = (user) => (dispatch) => {
    getAddress()
        .then((res) => {
            dispatch(setUserInfo(user));
        })
};

effects

function* fetchUserEffects() {
    const user = (yield call(getUserInfo)).data;
    yield put(fetchUser(user)); // modification point
    console.log('Class: fetchUserEffects, Function: fetchUserEffects, Line 8 yield select(): ', yield select(state => state.user));
}

As above, execution shows user information printed in effects is empty, and effect printing executes first

Changing put to putResolve

According to the official documentation, putResolve is blocking, so changing put to putResolve and executing:

You’ll find user information printed in effects is not empty, and reducer printing executes first. The difference between blocking and non-blocking is clear now.

Additional Notes

While clarifying the blocking/non-blocking issue, through component render printing and printing after put, I found that when put dispatches an action, the return status to effects is noticeably slower than what components listening to it receive. I think this makes sense. If we separate redux and effects, when we execute an action and modify store state, redux executes its own listener functions and notifies externally, so components know first. Of course, this is just my speculation.

Conclusion

  • Gained more understanding of put operations in saga - put operations don’t necessarily complete execution before moving to the next step.
  • Personal view: gradually understanding the technical details helps gradually master a technology.
  • When searching for information, I always find that although there’s a lot of material, it’s often repetitive, especially in Chinese. It’s better to have your own insights. Sharing or copying provides no benefit for personal improvement.

References