Fetch Error Handling: proposed new module #643
Replies: 5 comments 5 replies
-
I can't decide if I like it or not. So here's a bit of a brain monologue: I think a good question to ask is if people would actually take time to go back to the code that was written months / years ago to implement this. I'm inclined to think that's not going to be a super high priority work, maybe something that will live in the backlog for months and will get given to a new comer to the team. I am wondering about discoverability. You mention this would be one of many modules - there's a chance that people will forget it's there at the time they are setting up a new app - or never realise it exists in the first place. The reason this repeated code is in many places because the teams want customise handling - this allows them to set up more or less detailed (or more or less human-readable) messages, allowing them to note any idiosyncrasies into the messages (that may not apply to other systems). Having said this, I'd lean towards option 2, mainly to distinguish it from native fetch. |
Beta Was this translation helpful? Give feedback.
-
That's really nice @rowanmanning ! I would just change to have everything as options so we can switch on/off things like retry on error. I think the best solution will depends on the project really. I can see there will be cases that option 1 would be great, for example if you have a global fetch - you would only need to configure the wrapFetch once. However, I don't like the wrapFetch naming and of course I don't have a better naming to suggest 😄 Also, for legacy code, I think we can adopt the refactoring idea of: if we are going to change anything in a code that uses fetch and handles errors, let's replace with the new implementation - not doing all at once. Is n-fetch a good library to use an example? This would kit come for free for systems/libs using n-fetch. |
Beta Was this translation helpful? Give feedback.
-
Very well put Rowan 🙌 try {
const myInnocentJsonResponse = await fetch({ ... something ... })
+ .catch(handleNetworkErrors) // this takes care of comms issues
+ .then(handleFetchErrors) // this takes care of non-200 responses
.then(response => response.json())
console.log({ myInnocentJsonResponse })
} catch (error) { ... } Alternatively ... Can we supply a custom package that wraps |
Beta Was this translation helpful? Give feedback.
-
I guess |
Beta Was this translation helpful? Give feedback.
-
re the |
Beta Was this translation helpful? Give feedback.
-
Now that Reliability Kit has been around for a while, we're noticing some patterns in the way we need to handle errors. One of the things we've found we write a lot of boilerplate code for is handling errors that occur as a result of an HTTP request, e.g. via the
fetch
API.An example of this is in next-newsletters-page here. Often with fetches we find we need to do the following to get full and detailed error reporting for our HTTP requests:
UpstreamServiceError
OperationalError
- the current app has an issueUpstreamServiceError
with the upstream system code - the upstream service has an issueUpstreamServiceError
- the upstream app has an issueI'm proposing we wrap this logic into a new Reliability Kit module which can be used to wrap calls to
fetch
. Here's my current thinking on some options for the API.Option 1: wrap the fetch function
If you want the errors to be associated correctly with the upstream system that's erroring:
I think the positive of this approach is that once you have the generated
fetch
function the API is familiar – it's the same as regularfetch
. The negative is that it could be confusing to debug in a scenario where somebody doesn't realise that thisfetch
is not native code. We'd also have to cater for multiple differentfetch
implementations with different APIs, e.g. node-fetch supportstimeout
but native fetch usesAbortController
.Option 2: wrap the fetch promise
If you want the errors to be associated correctly with the upstream system that's erroring:
The positive of this approach is that you separate the error handling completely from the
fetch
implementation, which means that you don't need to cater for differences in third-party vs native APIs. As long as thehandleFetchErrors
function can deal with aResponse
-like object thenfetch
can be anything. The negative is that it'd be easy to make some small mistakes in implementing, e.g. what happens if the innerfetch
isawait
ed too? like:Update: with the above case we'd probably just error if the value passed in isn't a promise, something like:
Option 3: provide a rejection handler
Edit: this is an adaption of option 2 which was suggested by @vbachev in this comment and @wheresrhys in this comment, it's a little more verbose but probably the more idiomatic Node.js/promise-based approach.
We could provide a function which generates a rejection handler which can then be passed directly into
fetch().catch()
. However we'd also need a second handler for when thefetch
doesn't reject but it it's not OK:If you want the errors to be associated correctly with the upstream system that's erroring, we could expose a second method to generate a handler:
If you don't want to use
catch
/then
, it's also possible to use these methods like this, but I think it's harder to read (because you have to resolve whether the error is a fetch error or one thrown by the response handler):The positive of this approach is that you separate the error handling completely from the
fetch
implementation, like option 2. I think this reduces the possibility of it being implemented incorrectly as we're leaning more on native promise handling. The negative is the verbosity, and there's still a small chance that an implementer will use the wrong handler in thecatch
orthen
.It's also possible that someone will accidentally assign the
catch
/then
in the wrong order, which completely changes the way this would work.Feedback please
I'm interested in your thoughts on this. Are we over-engineering? Is this useful? Do you have a different opinion of how this could work? Thanks! In terms of options I'm leaning towards 2 I think, option 3 seemed good on the surface but actually ended up being more complex and boilerplate than I initially thought.
Beta Was this translation helpful? Give feedback.
All reactions