question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

I hope I’m not duplicating any issues, but from #50 I got the impression that the use-case of synchronous actions was already resolved in v0.6+.

I’m using a custom middleware to handle API calls, more or less like from the real-world example in redux. So I dispatch an object that might look like

function loadUserData(id) {
  return {
    types: [USERDATA_PENDING, USERDATA_SUCCESS, USERDATA_FAILURE],
    [API]: {
      endpoint: '/whatever/user'
    },
   meta: { userId: id }
  }
}

within the api middleware authentication, caching etc will be handled (I know the whole thing could be maybe handled with sagas, but for now we just try to migrate bits and pieces and see how it works out). Right the start of the middleware I dispatch the pending action

...
const [ pending, success, failure ] = action.types
dispatch({ type: pending, meta: action.meta })
...

In my saga now I listen to some user intent action (e.g. SHOW_PROFILE) from a component and try to make related calls along with other operations that might need to happen as a result

...
const { id } = yield take(SHOW_PROFILE)
(typeof id !== 'undefined') ? yield put(loadUserData) : yield put(<someOtherAction>)
const { meta: { userId }} = yield take(USERDATA_PENDING)
history.push(`/app/someurl/${userId}`)
...

in this scenario I miss the pending action, and the only way to take it is to wrap the dispatch call in the api middleware with a setTimeout function.

Is this case still a limitation of the library or am I missing something to handle these use-cases correctly?

Issue Analytics

  • State:closed
  • Created 8 years ago
  • Comments:10 (3 by maintainers)

github_iconTop GitHub Comments

3reactions
yelouaficommented, Jan 11, 2017

@baabgai First let’s understand why you’re missing the action.

First thing to know is that the put effect is dispatched asynchronously in a microtask (you’ll see why shortly); i.e. something like this

function runPutEffect(action) {
  Promise.resolve().then(() => dispatch(action))
}

what that means in your code is that yield put(loadUserData(...)) is not executed right away but shceduled in a microtask. so the current flow will first terminate (e.g. the code dispatching the action to the Saga) then the dispatch will be executed.

There are various reasons for this but in this case your example doesn’t work precisely because of synchronous dispatching. Here is why

Suppose that redux-saga executes the put effects syncronously (i.e right away)

function runPutEffect(action) {
  dispatch(action)
}

What will happen in your example above is the following sequence

  • Some code (e.g. inside an UI component) dispatches SHOW_PROFILE
  • The Saga takes the action and yields the put(loadUserData(...)) effect
  • (We suppose) the Saga dispatches the loadUserData(...) action synchronously
  • The action goes through the Store’s middleware pipeline and intercepted by your middleware
  • The middleware will dispatch({ type: pending, meta: action.meta }). and this is the issue: because we’re still in the middle of the first dispatch.

In other terms we’ve this call sequence

UIComponent:dispatch(SHOW_PROFILE)
-----------> Saga:take(SHOW_PROFILE)
-----------> Saga:put(loadUserData)
--------------------->Store:dispatch(loadUserData)
------------------------------->middleware:dispatch({ type: pending, meta: action.meta })
------------------------------->middleware:fires the ajax request
------------------------------->middleware:return
--------------------->Store:return
----------->Saga:take(USERDATA_PENDING)

so the issue here is precisely because the sequence is synchronous, when the Saga is calling put; It’ll have to wait for the dispatch call to return (Just as you wait for a val = func()). But since the middleware action is dispatched inside the first dispatch, the Saga will not take the nested action because it’s still waiting for the outer dispatch call to return.

and this is exactly what’s happening in your example above: even if the Saga dispatches the action asynchronously, the whole sequence above will happen inside the same microtask (the dispatch and the nested dispatch).

So the solution is to dispatch the middleware action after the the saga action and not inside of it. And to do this you have also to dispatch your action asynchronously

const [ pending, success, failure ] = action.types
Promise.resolve(). then( () => dispatch({ type: pending, meta: action.meta }) )

I got the impression that the use-case of synchronous actions was already resolved in v0.6+.

0.6 solved the case for synchronously taking actions: i.e. the take effects will be resolved in the same task as when the action was dispatched (e.g. from a Component event handler). But the case for sync/async dispatches is more complicated (conceptually and not technically) because of the nested dispatch issue above.

I opted for the async dispatch because it provides a more predictable behavior for most of use cases. For example a common use case is for Sagas to dispatch actions at the start of the application

function* startupSaga() {
  yield put( loadStartupData() )
}

//...
createStore(reducer, applyMiddleware( createSagaMiddleware(startupSaga, ...) ))

The Saga dispatches an action right at the start. If we dispatch the action synchronously, it won’t work because the code above will execute inside the applyMiddleware(...) call, i.e. before even the createStore returns.

You can also find more examples in those test cases

Is this case still a limitation of the library

TBH I can’t answer this. I agree it has to do with the event model of Sagas (take/put). But I think it’s also due to how Redux dispatching works.

Will this issue be gone if Redux flattened itself the dispatches (for example by ordering them in microtasks) ? I don’t know. But I’d like to hear what @gaearon is thinking of this

1reaction
baabgaicommented, Mar 17, 2016

Thanks for the suggestions, but this won’t work because I need a key which is included in the meta tag of the action to get the navigation correct. I forgot to include these requirements in the original post (now its updated). I’m sure there might be other ways to work around the problem, but I was also curious to know if this is somehow expected behavior or if I been missing something, especially since I’ve got the impression that after v0.6 this might work.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Missing in Action (film) - Wikipedia
Missing in Action is a 1984 American action film directed by Joseph Zito and starring Chuck Norris. It is set in the context...
Read more >
Missing in Action (1984) - IMDb
Colonel Braddock launches a mission deep into the jungles of Vietnam to find the POW camp that he escaped from and free the...
Read more >
Missing in Action - Rotten Tomatoes
Colonel James Braddock (Chuck Norris) survives a brutal stint in a Vietnamese POW camp, but he believes that even after the war ends...
Read more >
Amazon.com: Missing in Action - Collector's Edition [Blu-ray]
American servicemen are still being held captive in Vietnam—and it's up to one man to bring them home in this blistering, fast-paced action/adventure...
Read more >
Missing in Action: Trilogy (Blu-ray) - Kino Lorber Home Video
Braddock: Missing in Action III (1988) – When Colonel James Braddock (Norris) is told that his wife and 12-year-old son are still alive...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found