8
votes

I've searched high and low but can't find a clear answer.

I've managed to wrap my head around the mechanics of Redux, but when I've come to the point of API calls and async action creators, I'm stuck with middleware in context of Promises.

Can you help me get the mess right?

Cotradictory pieces of the puzzle giving me headache:

  1. One of YT tutorials says that natively Redux dispatch method does not support promises returned from action creators--hence the need for Redux Promise library (I know the project is probably dead now and the continuation is Redux Promise Middleware).

  2. Dan says in "What is the difference between redux-thunk and redux-promise?" I can use promises even without middleware--just manage them in the action creator.

  3. In other answers I found examples of using thunks where the action creator returned a... promise (later is was processed in the caller /dispatch(myActionCreator(params).then(...)/ So a promise can be returned by a thunk WITHOUT any redux-promise lib..?

  4. In "What is the difference between redux-thunk and redux-promise?", the accepted answer states Redux Thunk returns functions, whereas Redux Promise returns promises.. what the heck?

To wrap it up: what's the point of using Redux Promise or Redux Promise Middleware? Why does Redux alone not natively support promises?

Update:

I've just realized that in point 3 above I overlooked then() being attached to dispatch and not included in dispatch() args.

3

3 Answers

15
votes

The linked answers are correct, but I'll try to explain further.

A basic Redux store will only accept dispatching plain object actions:

store.dispatch({type : "ADD_TODO", text : "Buy milk"});

If you try to pass anything other than a plain object action, the store will throw an error:

store.dispatch(() => {});
// Error: "Actions must be plain objects. Use custom middleware for async actions."

Middleware form a pipeline around store.dispatch(), and each middleware can do anything it wants with whatever value was passed to dispatch: modify it, log it, delay it, or dispatch something else instead it. That means that a middleware can "teach" dispatch() how to accept something that's not a plain action object, by intercepting the value and doing something else instead.

So, redux-thunk "teaches" dispatch how to accept functions, by intercepting the function and calling it instead of passing it on to the reducers. redux-promise "teaches" dispatch how to accept promises, by intercepting the promise and dispatching actions when the promise resolves or rejects.

Normally, dispatch returns whatever action object was passed in. Because middleware wrap around dispatch, they can also change what value is being returned. redux-thunk will run the thunk function, and return whatever that thunk function returns. That lets you do useful things like return a promise from a thunk, and chain behavior from there:

dispatch(someThunkReturningAPromise())
    .then(() => {
        // Do more stuff here
    });

For more info on the topic, see the Redux FAQ entry on dealing with side effects, and the articles in the Redux Side Effects section of my React/Redux links list.

5
votes

When you call an action creator, one the very first line of the action creator function, you make the ajax request. That's a network request that is going to reach out to that JSON API.

The key part to understand is that when we make that request, we go down to the next line of code where we form up that action object and return it. The time between those two steps, between making the request and returning the action is instantaneous.

As you very well know, whenever we make a network request to some outside API, it may take some amount of time to get a response back.

So, after we return our action from the action creator, at some point in the future, we get a response back from the JSON API.

So, between Ajax request issued and Action returned from action creator may be instantaneous, but the time between Action being returned from action creator and response from JSON API received may take longer.

Regardless how long it takes, by the time the action shows up inside of the reducer, it always has our data available from our API.

To give you a better idea, I have added a debugger statement to one of my own reducers so we can look at the different values inside of there.

import { SAVE_COMMENT, FETCH_COMMENTS } from 'actions/types';

export default function(state = [], action) {
  switch (action.type) {
    case SAVE_COMMENT:
      return [...state, action.payload];
    case FETCH_COMMENTS:
      debugger;
      const comments = action.payload.data.map(comment => comment.name);
      return [...state, ...comments];
    default:
      return state;
  }
}

enter image description here

When I clicked the Fetch Comments button thats when it called the action creator and inside my sources tab I immediately hit the debugger statement.

Here is evidence that whenever this action shows up inside a reducer, it has the response that it got from the API.

enter image description here

Now, lets remove the Redux Promise middleware and see what happens.

Middleware:

export default ({ children, initialState = {} }) => {
  const store = createStore(
    reducers,
    initialState,
    applyMiddleware(reduxPromise)
  );

Middleware gone:

export default ({ children, initialState = {} }) => {
  const store = createStore(reducers, initialState, applyMiddleware());

  return <Provider store={store}>{children}</Provider>;
};

What's this?

enter image description here

The payload is not a response coming back from the JSON API, instead its a pending Promise, which means our request is still out on the network waiting to come back from the JSON API. So clearly, without Redux Promise middleware, our application will not work as expected.

Action creators were not developed to support asynchronous request, call it an oversight, I don't know.

We use middlewares like Redux Promise to look at actions that are about to be sent to a reducer and we have the opportunity to delay, log, modify or stop the action entirely and its only through these middlewares that we make these asynchronous requests work the way we expect it to. We are using Redux Promise because we want to inspect every action returned from an action creator and if it contains an API request or some asynchronous request, we want to delay it, so we can get that response to come back before the action goes on to the reducers. That is what Redux Promise does for us.

1
votes

you need those middlewares in order to avoid race conditioning because javascript is async. The differences between them are just the implementation, thunk works with functions, sagas with generators, etc