0

Currently, I'm using fetch with redux-thunk to read code from an API -

my code reads like this:

export function getUsers() {
  return (dispatch) => {
    // I have some helper code that automatically resolves the json promise
    return fetch(`/users`)
    .then((resp, json) => {
      if (resp.status === 200) {
        dispatch(getUsersSuccess(json));
      } else {
        dispatch(getUsersFail(json));
      }
    }).catch((err) => {
      // network error
      dispatch(getUsersFail(err));
    });
  };
}

The problem here is the catch method, as it will catch any error thrown in the then block. This commonly means that if some React component's render function fails with a programmer error, that error gets swallowed up back into dispatch(getUsersFail(err)).

Ideally, I'd like to detect if err is a fetch error (and dispatch my own action), otherwise throw. However, fetch throws a generic TypeError. How can I reliably detect that the error caught was one thrown by fetch?

killachaos
  • 414
  • 6
  • 15
  • What promise implementation are you using that will pass two arguments to a `then` callback? – Bergi Feb 22 '17 at 02:42
  • my "fetch" function is actually a wrapper around window.fetch - `f(u, opts).then(res => Promise.all([res, isJson(res) ? res.json() : {}]))`. It doesn't actually pass 2 arguments, but 1 argument that is an array, that I use the spread operator to expand `([resp, json]) =>` – killachaos Feb 22 '17 at 21:05
  • Ah, thought so. Btw, you might want to consider adding `if (!res.ok) return res.json().then(json => { throw json })` to your wrapper so that you don't need to detect the status code in your `getUsers` function, and could just write `fetch('/users').then(getUsersSuccess, getUsersFail).then(dispatch)` – Bergi Feb 22 '17 at 21:11

1 Answers1

0

Don't use .catch() but install the error handler directly on the fetch promise, as the second - onreject - argument to .then():

return fetch(`/users`)
.then(([resp, json]) => {
  if (resp.status === 200) {
    dispatch(getUsersSuccess(json));
  } else {
    dispatch(getUsersFail(json));
  }
}, (err) => {
  // network error
  dispatch(getUsersFail(err));
});

Check out the difference between .then(…).catch(…) and .then(…, …) for details.

Btw, I'd recommend to write

return fetch(`/users`)
.then(([resp, json]) => resp.status === 200 ? getUsersSuccess(json) : getUsersFail(json)
     , getUsersFail)
.then(dispatch);
Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • I *want* to catch the errors thrown by Fetch however. For example, if our API is down, or if the user's internet is spotty I want to communicate that error to the user. However, what I've found is that the call to ` dispatch(getUsersSuccess(json))` can throw for whatever reason (for example if a React component throws an error in a different part of the code, and the render method was in this chain) - I still want to able to throw those errors. – killachaos Feb 22 '17 at 21:10
  • @killachaos That's exactly what the code in my answer does. – Bergi Feb 22 '17 at 21:14
  • My mistake, I didn't actually test your code. Your link mentions this is an anti-pattern though however I'm not sure if I should ignore this - as I really want to avoid catching errors not related to that block of code. – killachaos Feb 22 '17 at 21:31
  • @killachaos The *answer* to the linked question explains why (when) it is *not* an antipattern :-) – Bergi Feb 22 '17 at 21:36
  • what do you think about specifying the catch first? i.e. `fetch(u).catch(err => networkFail(err)).then(getUsersSuccess)` it looks awkward but is more readable than `then(s,f)` for callbacks where we need to do some preprocessing before we dispatch. My only concern is it doesn't look idiomatic and it isn't immediately clear why the order matters here. – killachaos Feb 22 '17 at 23:57
  • Actually that doesn't fully work, as the then() will still get resolved. – killachaos Feb 23 '17 at 00:03
  • Yes, to always get either of the two callbacks executed and never both, you have to pass them to the same `then` call. (The only exception to that rule is when the rejection callback *always* throws, in that case you could do both `.then(x, thrower)` and `.catch(thrower).then(x)`) – Bergi Feb 23 '17 at 00:37