Making JSON requests to fetch relationships asynchronously
First, I would get that error check out of the way instead of in the middle of the operation. Just throw an error in the middle of the operation and let the catch handle the dispatch.

Also, fetch will always resolve. A common procedure is to use the very first then to evaluate the response and determine how the sequence proceeds. It's usually either resolving with the data or rejecting with the error. But in your case, since both success and error actions dispatch body, the only difference is the action creator. Instead of a resolution/rejection, we just resolve with the correct action based on ok.

export const fetchRelationships = url =&gt; {
  return async dispatch =&gt; {
    dispatch(fetchRelationshipsRequest())

    fetch(url, config)
      .then(res =&gt;
        // We cannot return res.json() and do its then as part of the
        // outer sequence since we need res. This needs to nest.
        res.json().then(body =&gt; 
          res.ok ? fetchRelationshipsSuccess(body) : fetchRelationshipsError(body)))
      .then(action =&gt; dispatch(action))
      .catch((error) =&gt; dispatch(genericErrorAction())
  }
}


Now for async-await. The returned anonymous function from fetchRelationships will have to be async in order for its body to be able to use await. All promise-returning functions will need to use await. And then turn everything into synchronous-looking code. Here's the same procedure as above, but in async-await form.

export const fetchRelationships = url =&gt; {
  return dispatch =&gt; {
    dispatch(fetchRelationshipsRequest())

    try {
      const res = await fetch(url, config)
      const body = await res.json()
      const action = res.ok ? fetchRelationshipsSuccess(body) : fetchRelationshipsError(body)
      dispatch(action)
    } catch(error){
      dispatch(genericErrorAction())
    }
  }
}