Handling error cases in fetch javascript
This is a guide on using default fetch API. We will explore handling server errors, GET param and aborting requests in-flight.
Problem with default fetch
In js, the default way to make an API call is to use fetch and the default syntax goes something like this.
async function getTodos({title, id}) {
let url = 'https://jsonplaceholder.typicode.com/todos'
if(title){
url += `?title=${title}`
}
if(id) {
url += `&id=${id}`
}
try {
const res = await fetch(url)
const todos = await res.json()
console.log(todos)
} catch {
console.log('some error')
}
}
getTodos({title: 'lorem', id: 1}) // works
Here there are 2 potential problems
Error codes like 400, 404, 401 and 500 are unhandled.
Upon undefined or non-encoded get params. eg
getTodos({title: 'lorem ipsum', id: 1})
orgetTodos({id: 1})
Handling Error codes
Let’s remove the params because we are purely focusing on handing error
async function getTodos({userId, id}) {
let url = 'https://jsonplaceholder.typicode.com/todos'
try {
const res = await fetch(url)
if(!res.ok) {
throw Error(response.statusText);
}
const todos = await res.json()
} catch {
console.log('some error')
}
}
getTodos({userId: 1, id: 1})
Basically, server error in fetch wouldn’t reach the catch block. Instead, we need we manually check res.ok
to see if we are good to go.
So then what would reach the catch block?
Consider the following code.
const controller = new AbortController();
const signal = controller.signal;
const cancelButton = $.querySelector('#cancel')
cancelButton.onClick = () => controller.abort()
try {
const url = 'https://httpbin.org/delay/1';
const response = await fetch(url, { signal });
} catch (error) {
// DOMException: The user aborted a request.
console.log('Error: ', error)
}
It would reach catch block in 2 scenarios when we use a controller.abort or due to network failure.
In our case when the user clicks on the cancel button. (
DOM Error: Request abort by user
)When the user's device went offline while using the app. (
DOM Error: Failed to fetch
)
A better way to handle GET params
let url = new Url('https://jsonplaceholder.typicode.com/todos')
if(title){
url.searchParams.set('title', title)
}
if(id) {
url.searchParams.set('id', id)
}
This solves 2 problem
If the search is undefined, the URL would still be
https://jsonplaceholder.typicode.com/todos?id=1
and not be&id=1
.Params would be encoded. For eg, if the title is
lorem ipsum
it would behttps://jsonplaceholder.typicode.com/todos?title=lorem+ipsum
Final Version
const handleError = (res) => {
if(res.ok) {
return res
}
throw Error(res.statusText)
}
const constructSearch = (params) => {
const search = new URLSearchParams()
const dropNA = ([key, val]) => key != null && val != null
const updateParam = ([key, val]) => {
search.set(key, val)
}
Object.entries(params).filter(dropNA).forEach(updateParam)
return search
}
const get = async (urlString, params, signal) => {
let url = new URL(urlString, signal)
url.search = constructSearch(params)
try {
const res = await fetch(url)
handleError(res)
const data = await res.json()
return data;
} catch(e) {
console.log(e)
throw e;
}
}
const todoURL = 'https://jsonplaceholder.typicode.com/todos'
const getTodo = async (params) => get(todoURL, params)
getTodos({title: 'lorem', id: 1}) // works
getTodos({title: 'lorem ipsum', id: 1}) // works
getTodos({id: 1}) // works
When I knew about this nightmare, my brain was like.
So, what about Axios?