Redux middleware async function
Asked Answered
B

1

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

Bisulcate answered 8/10, 2017 at 17:35 Comment(2)
internally, you don't really do anything different thank calling next when resolved, so I don't see why it wouldn't workCarborundum
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.Trocar
S
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.

Selflove answered 3/12, 2020 at 9:41 Comment(7)
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?Trocar
@Hey Tom! Updated answer with a possible solution using redux thunks. :)Sunnisunnite
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).Trocar
@Trocar 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.Vick
@Vick 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." ?Trocar
@Trocar 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.Vick
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 2Trocar

© 2022 - 2024 — McMap. All rights reserved.