I am writing a helper for my API calls and would like it to
- pass a
text()
response when the call is successful - throw the full Response otherwise
The reason for this is that I have a case of an API call where a 404
is expected and would like to manage this specific case. Other 404
(in different calls) are indeed errors.
Consider the code below:
const apiCall = (url) => {
return fetch(url)
.then(r => {
if (r.ok) {
return r.text()
} else {
console.log('in apiCall → then(), fetch soft-failed, passing error downstream')
throw Error(r.statusText) // this works but is not what I want, I want to throw "r"
}
})
.catch(err => {
console.log('in apiCall → catch(), passing error downstream')
throw err
})
}
apiCall('')
.then(r => console.log(r))
.catch(err => console.log(`the return code was ${err}`))
apiCall('')
.then(r => console.log(r))
.catch(err => console.log(`the return code was ${err}`)) // this works but I obviously got a statusText and not an r
I am writing a helper for my API calls and would like it to
- pass a
text()
response when the call is successful - throw the full Response otherwise
The reason for this is that I have a case of an API call where a 404
is expected and would like to manage this specific case. Other 404
(in different calls) are indeed errors.
Consider the code below:
const apiCall = (url) => {
return fetch(url)
.then(r => {
if (r.ok) {
return r.text()
} else {
console.log('in apiCall → then(), fetch soft-failed, passing error downstream')
throw Error(r.statusText) // this works but is not what I want, I want to throw "r"
}
})
.catch(err => {
console.log('in apiCall → catch(), passing error downstream')
throw err
})
}
apiCall('http://httpstat.us/200')
.then(r => console.log(r))
.catch(err => console.log(`the return code was ${err}`))
apiCall('http://httpstat.us/404')
.then(r => console.log(r))
.catch(err => console.log(`the return code was ${err}`)) // this works but I obviously got a statusText and not an r
This outputs
in apiCall → then(), fetch soft-failed, passing error downstream
in apiCall → catch(), passing error downstream
the return code was Error: Not Found
200 OK
What I would like to do is for one call where the 404
is expected (this is incorrect code in the context of the code above)
apiCall('http://httpstat.us/404')
.then(r => console.log(r))
.catch(err => {
if (err.Code == 404) {
// it is OK
} else {
// it is not OK
}
})
and for another call where any non-2xx response is incorrect :
apiCall('http://httpstat.us/404')
.then(r => console.log(r))
.catch(err => {
// not OK
})
How can I throw
the Response and not only text?
In other words, the code I would like to have:
const apiCall = (url) => {
return fetch(url)
.then(r => {
if (r.ok) {
return r.text()
} else {
console.log('in apiCall → then(), fetch soft-failed, passing error downstream')
throw r
}
})
.catch(err => {
console.log('in apiCall → catch(), passing error downstream')
throw err // I would need to manage actual errors (network, ...)
})
}
apiCall('http://httpstat.us/200')
.then(r => console.log(r))
.catch(err => console.log(`the return code was ${err.Code}`))
apiCall('http://httpstat.us/404')
.then(r => console.log(r))
.catch(err => console.log(`the return code was ${err.Code}`))
but this outputs undefined
in apiCall → then(), fetch soft-failed, passing error downstream
in apiCall → catch(), passing error downstream
the return code was undefined
200 OK
Share
Improve this question
edited Mar 13, 2022 at 14:12
WoJ
asked Mar 13, 2022 at 14:03
WoJWoJ
30.2k58 gold badges214 silver badges406 bronze badges
2 Answers
Reset to default 2You can add an argument as expectedFailureCodes
and check the request code after it fails. If it is well expected then you can handle it as you like.
import fetch from "node-fetch";
const apiCall = (url, expectedFailureCodes=[]) => {
return fetch(url)
.then(async res => {
if (res.ok) return res
else if (expectedFailureCodes.includes(res.status)) {
return {
passedIntentionally: true,
res
}
}
else throw new Error(JSON.stringify({
status: res.status,
body: await res.text()
}))
})
.catch(err => {
throw err
})
}
apiCall("http://httpstat.us/404", [404]).then(res => {
console.log(res)
})
apiCall("http://httpstat.us/404", ).catch(err => {
console.log(err)
})
apiCall("http://httpstat.us/200", ).then(res => {
console.log(res)
})
Well, throw
ing r
works fine, you were just logging the wrong property. The status code can be accessed via r.status
:
const apiCall = (url) => {
return fetch(url)
.then(r => {
if (r.ok) {
return r.text()
} else {
console.log('in apiCall → then(), fetch soft-failed, passing error downstream')
throw r
}
})
.catch(err => {
console.log('in apiCall → catch(), passing error downstream')
throw err // I would need to manage actual errors (network, ...)
})
}
apiCall('http://httpstat.us/200')
.then(r => console.log(r))
.catch(err => console.log(`the return code was ${err.status}`))
apiCall('http://httpstat.us/404')
.then(r => console.log(r))
.catch(err => console.log(`the return code was ${err.status}`))
The only problem with that approach is, that your code won't know how to handle "hard errors" (network errors or runtime errors in the code). You can identify those with "duck typing methods" (if it has a status
property, it must be a Response object...), but a better solution would be to have a custom Error class:
class HTTPError extends Error{
constructor(r){
super(`HTTP ${r.status} error`)
this.r = r
}
}
const apiCall = (url) => {
return fetch(url)
.then(r => {
if (r.ok) {
return r.text()
} else {
console.log('in apiCall → then(), fetch soft-failed, passing error downstream')
throw new HTTPError(r)
}
})
.catch(err => {
console.log('in apiCall → catch(), passing error downstream')
throw err
})
}
apiCall('http://httpstat.us/200')
.then(r => console.log(r))
.catch(err => {
if(!(err instanceof HTTPError))
throw err //Other error
console.log(`the return code was ${err.r.status}`)
})
apiCall('http://httpstat.us/404')
.then(r => console.log(r))
.catch(err => {
if(!(err instanceof HTTPError))
throw err //Other error
//You can access `r` via `err.r` here
console.log(`the return code was ${err.r.status}`)
})