Redux: Dispatching Actions Asynchronously with Thunks

Share this video with your friends

Send Tweet

We will learn about “thunks”—the most common way to write async action creators in Redux.

Dmitry
Dmitry
~ 8 years ago

What about redux-saga? It looks like nice alternative to thunks.

Ken Snyder
Ken Snyder
~ 7 years ago

I love thunks and use them regularly but I find myself often wanting "composable" or "thennable" thunks. Is that a thing? Let me explain what I'm looking for ... I want to call two action creators and both are thunks but they have an synchronous dependency between them (aka, B must follow A).

I'd want the code to look something like:

dispatch(actionCreator.A()) .then(dispatch(actionCreator.B())

Charlie
Charlie
~ 7 years ago

Why explicitly pass store.dispatch, rather than next, to action? For example, this is the recommended code:

const thunk = (store) => (next) => (action) => {
  (typeof action === 'function') ?
    action(store.dispatch) :
    next(action);

Is the above objectively better than:

const thunk = (store) => (next) => (action) => {
  (typeof action === 'function') ?
    action(next) : // Passing next instead of store.dispatch
    next(action);

Thanks!

Dnamic
Dnamic
~ 7 years ago

For some reason when the video gets to 2:57 the video crashes and it can't be seen.

Nino Harris
Nino Harris
~ 7 years ago

Why explicitly pass store.dispatch, rather than next, to action? For example, this is the recommended code:

We call store.dispatch to 'restart' the process of sending the action from the first middleware onwards, rather than passing it to next where the next middleware will interpret/dispatch it.

Nino Harris
Nino Harris
~ 7 years ago
J. Matthew
J. Matthew
~ 5 years ago

We call store.dispatch to 'restart' the process of sending the action from the first middleware onwards, rather than passing it to next where the next middleware will interpret/dispatch it.

I want to follow up on the question from @Charlie and this response, because the question is one I've really struggled with, and I don't think this response is quite right, though it seems like it's on the right track.

You may recall that, at the beginning of "The Middleware Chain" lesson, what we now call next was called rawDispatch, and it was not passed into the function, but rather extracted from store within the function. In the course of the lesson, Dan refactored the function to implement the standard "middleware contract" structure, which takes store, next, and action as curried arguments. next replaces the earlier rawDispatch, and by passing it as an argument separate from store, it becomes decoupled—you could use store.dispatch, but you don't have to.

I commented at the time that this doesn't offer any obvious benefit, at least in the lesson, because when that function actually gets called as one of the middlewares, store.dispatch gets passed as next! So, functionally, it's the same as before, and I didn't understand why we should bother doing this.

I talked about this with a friend who has more experience with currying, and he pointed out that there might be instances (such as unit tests on the middleware itself) where you would want to use a different function for next than whatever store.dispatch is, without actually changing store. This makes sense to me in the abstract, though it would really help me to see an example.

However, this only requires that you separate next from store as arguments, not that they be curried necessarily. As was pointed out in those previous comments, you could accomplish the same goal with
const myMiddleware = (store, next) => (action) =>
as you get with the existing pattern
const myMiddleware = (store) => (next) => (action) =>

My friend concurred with this point. The benefit of currying is that you don't have to know all your argument values at the same time. When the middleware gets called, as previously linked, it's invoked with store and then the returned function is immediately also invoked with next, in the form of store.dispatch. So there's no benefit to currying in that example. But it's conceivable that you could have a scenario in which you only want to invoke the middleware with store and then save the resultant function in a variable, to be invoked later, when you have whatever next you want to pass. Again, this make sense to me conceptually, and even agrees with the limited ways in which I have made my own use of currying, but it would be helpful to see an example of that use-case applied here.

All of this gets me to a point where I understand why it might be valuable to decouple next from store, and even to then curry next instead of passing it as a second argument to the function that takes store (though examples would be so helpful). Which brings me to the question and response I referenced at the top.

I don't think @Nino Harris is quite right to say "We call store.dispatch to 'restart' the process of sending the action from the first middleware onwards," because store.dispatch only references whatever function is currently attached to that property. As we've seen, the middleware chain reassigns the property for each new middleware, wrapping the current functionality in some additional layer of functionality.

Now, it is true that, as the lesson code is written, we're passing thunk as the first middleware, so within that function, store.dispatch happens to refer to the default dispatch function that comes with Redux. But that's external knowledge we have that results from the order we happened to pass the middlewares in; the thunk code itself shouldn't have any awareness of what store.dispatch is or whether it's already including other middlewares.

And as for the second part of the quote, "rather than passing it to next where the next middleware will interpret/dispatch it," I find this confusing because, as pointed out before, next is store.dispatch in this instance. So it doesn't actually make any functional difference as this function is used in this example whether we call action(next) as @Charlie suggested or action(store.dispatch) as the code is written. Yet the code explicitly makes the point to pass store.dispatch rather than next to action, and I just can't figure out why.

I think what @Nino Harris was trying to get at is that store.dispatch is used to refer to some prior or current state of the dispatch function, rather than whatever the next version of that function would be. And what he said about making sure that the actions the thunk is dispatching get sent through the beginning of the middleware chain rather that from some midpoint does make sense to me. But I just don't see how the code is actually doing any of that.

Based on my conversation with my friend, it seems like the only justification for this approach might be if, again, you were in some unit-test or other scenario in which you wanted to pass a different value to next than what's referenced by store.dispatch, yet it was somehow important that, within thunk, it was the latter and not the former being passed to action. I can't even begin to work through that one in my head.

I find this sort of thing very difficult to think through. Perhaps I'm missing something in the existing example. If anyone understands this well, I would really appreciate an example of using thunk in a scenario where store.dispatch and next are not the same, and it matters and is beneficial that the one is passed to action and not the other.

J. Matthew
J. Matthew
~ 5 years ago

we're passing thunk as the first middleware, so within that function, store.dispatch happens to refer to the default dispatch function that comes with Redux

Actually, my statement here isn't even correct—I remembered that the middlewares array is processed in reverse order, so logger gets applied to store.dispatch before it makes it to thunk. Thus both store.dispatch and next in the thunk code are referring to logger.

This makes even less sense to me. Dan said thunks could dispatch other thunks, but the thunk middleware wraps logger, yet the dispatch function that we're passing to action is logger. It seems like this means that the dispatch available inside fetchTodos wouldn't have that thunk middleware wrapping, so any thunk action it tried to dispatch would fail. I'm so confused. I feel like I must be missing something.

J. Matthew
J. Matthew
~ 5 years ago

I love thunks and use them regularly but I find myself often wanting "composable" or "thennable" thunks. Is that a thing? Let me explain what I'm looking for ... I want to call two action creators and both are thunks but they have an synchronous dependency between them (aka, B must follow A).

@Ken Snyder I think the code in this lesson already gives you that capability, unless I'm misunderstanding you.

The fetchTodos action creator currently returns a promise. So you can do this:

fetchTodos(filter).then(someThunk);
// e.g.
fetchTodos('active').then(todos => fetchTodos('completed'));

Alternatively, you could handle the dispatch within fetchTodos, by modifying the last line to be something like this:

.then(response => dispatch(receiveTodos(filter, response)))
.then(action => dispatch(someThunk(action)));

I've tested them both out and they work as expected.