3

I have the following promise chain:

return fetch(request)
  .then(checkStatus)
  .then(response => response.json())
  .then(json => ({ response: json }))
  .catch(error => ({ error }))

Where checkstatus() checks if the request was successful, and returns an error if it wasn't. This error will be caught and returned. But, the problem is that I want to add the both response.statusText and the results of response.json() to the error. The problem is that when I parse it I lose the original response in the chain since I have to return response.json() because it's a promise.

This is what checkstatus does currently:

const checkStatus = response => {
  if (response.ok) return response

  const error = new Error('Response is not ok')

  // this works because the response hasn't been parsed yet
  if (response.statusText) error.message = response.statusText

  // an error response from our api will include errors, but these are
  // not available here since response.json() hasn't been called
  if (response.errors) error.errors = response.errors

  throw error
}

export default checkStatus

How do I return an error with error.message = response.statusText and error.errors = response.json().errors?

  • @Bergi This question is specifically about how to handle errors in fetch well, which is arguably less trivial than it should be. I therfore think this problem deserves its own question and should not be closed as a dup of a more general organization question. – jib Jan 08 '17 at 18:17
  • @jib "*The problem is that when I parse it I lose the original response in the chain since I have to return `response.json()` because it's a promise*" is exactly what the canonical question is about – Bergi Jan 08 '17 at 19:23
  • 1
    @Bergi The solution proposed here is different and in my opinion more clear though. –  Jan 09 '17 at 11:59

2 Answers2

2

Here's my helper for sane fetch error handling, fetchOk:

let fetchOk = (...args) => fetch(...args)
  .then(res => res.ok ? res : res.json().then(data => {
    throw Object.assign(new Error(data.error_message), {name: res.statusText});
  }));

Which I then substitute for fetch.

let fetchOk = (...args) => fetch(...args)
  .then(res => res.ok ? res : res.json().then(data => {
    throw Object.assign(new Error(data.error_message), {name: res.statusText});
  }));

fetchOk("https://api.stackexchange.com/2.2/blah")
  .then(response => response.json())
  .catch(e => console.log(e)); // Bad Request: no method found with this name

var console = { log: msg => div.innerHTML += msg + "<br>" };
<div id="div"></div>

It doesn't load the data unless there's an error, making it a direct replacement.

jib
  • 40,579
  • 17
  • 100
  • 158
1

I use the new async/await syntax, since that reads in a more intuitive way:

 async fetchData(request) {
    try {
      const response = await fetch(request)
      const data = await response.json()

      // return the data if the response was ok
      if (response.ok) return { data }

      // otherwise return an error with the error data
      const error = new Error(response.statusText)
      if (data.errors) error.errors = data.errors

      throw error
    } catch (error) {
      return { error }
    }
  }

It makes it very easy to handle both the promise that fetch returns as well as the promise that response.json() returns.