1

I'm trying to understand async actions in redux. Reading this page I got the following example:

export function fetchPosts(subreddit) {
  return (dispatch) => {
​
    dispatch(requestPosts(subreddit))
​
    return fetch(`https://www.reddit.com/r/${subreddit}.json`)
      .then(
        response => response.json(),
        // Do not use catch, because that will also catch
        // any errors in the dispatch and resulting render,
        // causing a loop of 'Unexpected batch number' errors.
        // https://github.com/facebook/react/issues/6895
        error => console.log('An error occurred.', error)
      )
      .then(json =>
        dispatch(receivePosts(subreddit, json))
      )
  }
}

About that comment, it is a common error to swallow react exceptions, what I'm trying to avoid... I'm trying to use the new javascript async/await syntax here... how the equivalent code with the exactly same behavior would be?

I thought about this first:

export function fetchPosts(subreddit) {
​
  return async (dispatch) => {

    dispatch(requestPosts(subreddit));
    try {
      const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
      const json = await response.json();
      dispatch(receivePosts(subreddit, json));
    }
    catch (error) {
      console.log('An error occurred.', error);
    }
  }
}

But I had the feeling that this is exactly what that comment tells to avoid. Then I thought about this code:

export function fetchPosts(subreddit) {
​
  return async (dispatch) => {

    dispatch(requestPosts(subreddit));

    try {
      const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
      const json = await response.json();
    }
    catch (error) {
      console.log('An error occurred.', error);
      return;
    }

    dispatch(receivePosts(subreddit, json));
  }
}

But in the case of an error, I'm not sure if the behavior is the same as the example without async/await. I'm not sure if I need to put that return inside the catch block. And the example returns a promise, I'm not sure if it still happens with my code.

Searching about it, I only found this question but without response, and I found a redux-saga component that uses generators/yield syntax. Should I use redux-saga instead of redux-thunk with async/await?

lmcarreiro
  • 5,312
  • 7
  • 36
  • 63
  • Does `dispatch` throw an exception? If yes, in an `async function` it will always cause a rejection of the returned promise, you can't really avoid that. – Bergi Jul 31 '18 at 16:19
  • The `dispatch` call may or may not throw an exception. I'm not trying to avoid that, I'm trying to have the same behavior as the first code in both cases with and without an exception being thrown by the `dispatch` call. – lmcarreiro Jul 31 '18 at 16:22
  • You cannot, as the first code will throw an exception if the first `dispatch` call throws (not the one in the `then` callback), but an `async function` (like you intend to use) will *never* throw an exception. If you are fine with getting those as rejections (but not catching them, as the original code) then your attempt seems fine. – Bergi Jul 31 '18 at 16:25
  • That said, the original code is broken already, and does not really follow the advise from the linked react issue. It says that you should not catch and swallow exceptions from `setState` (and `dispatch` respectively, probably), but the code wasn't doing that anyway. Now, however, it totally *ignores* errors and calls `dispatch(receivePosts(…))` even when no `json` could be fetched! – Bergi Jul 31 '18 at 16:28

2 Answers2

1

Yes, I believe your second example is equivalent to the promise-based snippet, although there's a syntax error.

You're catching only errors from the fetch() call itself, and in the case of an error, logging it and stopping there. Assuming there was no error, it dispatches the action. And yes, all async functions automatically return a promise.

The error is that the const json = await response.json() is block-scoped, so the json variable won't be accessible outside the try {} block. You'd want to declare let json; before the try, so that you can refer to it afterwards.

markerikson
  • 63,178
  • 10
  • 141
  • 157
1

My feeling is that the original code should actually have been

export function fetchPosts(subreddit) {
  return (dispatch) => {
    dispatch(requestPosts(subreddit));
    return fetch(`https://www.reddit.com/r/${subreddit}.json`)
      .then(response =>
        response.json()
      )
      .then(json =>
        dispatch(receivePosts(subreddit, json))
      , error => {
        // Do not use catch, because that will also catch
        // any errors in the dispatch and resulting render,
        // causing a loop of 'Unexpected batch number' errors.
        // https://github.com/facebook/react/issues/6895
        console.log('An error occurred.', error)
      });
  }
}

where the error handler is the alternative to the dispatch of receivePosts(subreddit, json), instead of the alternative to the JSON parsing (and unconditionally followed by the dispatch with a possibly undefined json value).

This kind of branching is hard to achieve with try/catch when using async/await, so I would just keep the then syntax. If you want to rewrite it, your second attempt is fine (and equivalent to my corrected then syntax), but you would need to declare json outside of the try block (or use var instead of const):

export function fetchPosts(subreddit) {
  return async (dispatch) => {
    dispatch(requestPosts(subreddit));
    let json;
//  ^^^^^^^^
    try {
      const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
      json = await response.json();
    } catch (error) {
      console.log('An error occurred.', error);
      return;
    }
    dispatch(receivePosts(subreddit, json));
  }
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375