12

I have a redux middleware that interacts with a rest api. I recently started rewriting some fetch functions using async/await.

For this to make the most sense for me, I would need the middleware function itself to be an async function so that I can use "await" and try/catch blocks on those other async functions I have created as opposed to having to use .then, .catch.

So far I have this:

const apiMiddleware = ({
  dispatch,
  getState
}) => next => async action => {
  switch (action.type) {
   //...
   }
  next(action);
};

Please note the use of async keyword before "action". So far this seems to be working as expected, I am able to await other async functions from that middleware. However, since I was not able to find documentation on this, I was wondering if what I did truly is valid.

Thank you

roboris
  • 362
  • 2
  • 9
  • 3
    internally, you don't really do anything different thank calling next when resolved, so I don't see why it wouldn't work – Icepickle Oct 09 '17 at 08:37
  • We have multiple of such async middleware functions and recently started experiencing unexpected behavior as a result of it. We saw redux selectors being triggered with old pieces of state, presumably because the async middleware functions affected the order at which actions were processed. – Tom Nov 29 '20 at 01:02

1 Answers1

6

Have you considered looking into Redux Sagas? https://redux-saga.js.org/

This is specifically created for making working with async functions more manageable. And i would think that implementing this, can make your async functions more understandable, and easier to debug.

Your code is valid javascript, but you might experience inconsistency in the state, because of the async nature of your functions.

If your async action isn't finished before your next action is dispatched, the second action will use a version of the state, that might be mutated by the first action, while running.

This behavior can give you issues like you described.


Edit: After i learned you are already using redux-thunks

In redux thunks, you can chain actions dispatches, with await, and leveraging the return of a dispatch.

An example on how to chain actions with redux-thunks from this article: https://blog.jscrambler.com/async-dispatch-chaining-with-redux-thunk/

const dispatchChaining = () => async (dispatch) => {
  await Promise.all([
    dispatch(loadPosts()), // <-- async dispatch chaining in action
    dispatch(loadProfile())
  ]);

  return dispatch(updateDone());
};

const actions = redux.bindActionCreators({dispatchChaining}, store.dispatch);
actions.dispatchChaining().then(() => unsubscribe());

Note that as long as there is a return these dispatches are thenable. The bonus here is we can fire async dispatches in parallel and wait for both to finish. Then, update isDone knowing both calls are done without any unpredictable behavior. These reusable thunks can live in different parts of the store to maintain separation of concerns.

Bjørn Nyborg
  • 993
  • 7
  • 17
  • Hey Bjorn, your explanation on why this weird behavior is happening is spot on. However, we're already using redux-thunk for our async actions. The problem is that we're not trying to dispatch an async action here, we're trying to create async middleware, which may delay the execution of an action. Does that make sense? – Tom Dec 04 '20 at 02:28
  • @Hey Tom! Updated answer with a possible solution using redux thunks. :) – Bjørn Nyborg Dec 04 '20 at 07:25
  • Thanks Bjorn. This explains how to create new actions that chain other async actions, but the problem is we're trying to create async middleware. Our use case is a middleware that ensures that we are authenticated before running any redux actions that require authentication. If we are not authenticated, all redux actions should be halted until we are authenticated, and then actions should resume as usual. We're trying to achieve this halting of actions using an async middleware function (not an action). – Tom Dec 05 '20 at 17:43
  • @Tom I may sound lame but can you explain the meaning of 'redux selectors being triggered with old pieces of state' because selectors should only be triggered when there is change in relevant part of the state tree which is single source of truth. – deepanshu Dec 07 '20 at 16:05
  • @deepanshu isn't this what Bjorn said with "Your code is valid javascript, but you might experience inconsistency in the state, because of the async nature of your functions. If your async action isn't finished before your next action is dispatched, the second action will use a version of the state, that might be mutated by the first action, while running." ? – Tom Dec 08 '20 at 18:56
  • @Tom sorry to bother you again but, is your problem is that your selectors are getting triggered with same state value, e.g. if state=1 then they are called again with state=1. – deepanshu Dec 09 '20 at 18:48
  • the selectors are being triggered with an old value, so if state=1, then state=2, a selector may be triggered with state=1 where it should be 2 – Tom Dec 10 '20 at 20:11