kwhitley / itty-fetcher

An even simpler wrapper around native Fetch to strip boilerplate from your fetching code!
MIT License
99 stars 4 forks source link

COMMUNITY DISCUSSION: handling errors within an `await` block #41

Closed kwhitley closed 1 year ago

kwhitley commented 1 year ago

The Problem

Currently, it's challenging to handle error states when you're awaiting a response that may return an error. Errors intentionally throw by design within itty-fetcher. This gives you an easy way to intercept and handle them, but what if you wanted a pattern like this?

let { error, ...response } = await fetcher().get('https://itty.dev/missing')

This would currently require a custom handleResponse function, which would replace the existing one (that handles body parsing, etc). This requires duplication of internal logic, just to split out a payload. To alleviate this, we will attempt to parse the response body, even for error responses (in case the body is a detailed error message). This will pass through in the error thrown.

For instance, an error response from the API that looks like this:

400 {
  error: 'Missing username.'
}

would produce/throw an error like this in itty-fetcher:

.catch(err => {
  err.status // 400
  err.error // 'Missing username.'
})

With this underlying behavior in mind, I'd like to discuss some possible interface options:

// Option 1: if we do not throw by default
// Cons: breaking change
let { error, ...response } = await fetcher().get('https://itty.dev/missing')

// Option 2: we allow option to disable throwing
// Cons: extra bytes
let { error, ...response } = await fetcher({ throw: false }).get('https://itty.dev/missing')

// Option 3: we leave it as-is... you can rethrow and catch
let { error, ...response } = await fetcher().get('https://itty.dev/missing').catch(e => ({ error: e }))

I'm leaning towards the latter for now...