I wrote a function to catch non-200 results with fetch:
1 function $get(url, callback) {
2 fetch(url, {credentials: "same-origin"})
3 .then(resp => {
4 if (!resp.ok) {
5 resp.text().then((mesg) => {
6 throw {"stat": resp.status, "mesg": mesg.trim()}
7 })
8 return resp.text()
9 }
10 return resp.json()
11 })
12 .then(data => callback({"stat": 200, "data": data}))
13 .catch(error => callback(error))
14}
I got error on line 9:
ERROR: TypeError: Failed to execute 'text' on 'Response': body stream already read
The reason that I have to write code shown in line 5~7 is that if I wrote:
if (!resp.ok) {
throw {"stat": resp.status, "mesg": resp.statusText}
return resp.json()
I will get error message like {"stat": 403, "mesg": "Forbidden"}
, while what I want is:
{"stat": 403, "mesg": "invalid user name or password"}
.
On the server side my go program will generate non-200 reply like this:
> GET /api/login?u=asdf&p=asdf HTTP/1.1
> Host: localhost:7887
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 403 Forbidden
< Content-Type: text/plain; charset=utf-8
< X-Content-Type-Options: nosniff
< Date: Sat, 17 Jul 2021 11:53:16 GMT
< Content-Length: 25
<
invalid username or password
I.e. the go library do not modify http status text, instead, put error message in body, which maybe mandated by the http standard (e.g. status text cannot be changed).
So, my question is, either:
=== EDIT ===
The following code works OK, however, it seems to use "anti" pattern as pointed out by comments:
function $get(url, callback) {
fetch(url, {credentials: "same-origin"})
.then(resp => {
if (!resp.ok) {
resp.text().then((mesg) => {
callback({"stat": resp.status, "mesg": mesg.trim()})
})
return new Promise(function(_, _) {})
}
return resp.json()
})
.then(data => callback({"stat": 200, "data": data}))
.catch(error => { console.log(`GET ${url}\nERROR: ${error}`) })
}
However, this doe not work:
function $get(url, callback) {
fetch(url, {credentials: "same-origin"})
.then(resp => {
if (!resp.ok) {
resp.text().then((mesg) => {
throw `{"stat": resp.status, "mesg": mesg.trim()}`
})
}
return resp.json()
})
.then(data => callback({"stat": 200, "data": data}))
.catch(error => { console.log(`GET ${url}\nERROR: ${error}`) })
}
The throw
will generate this error, instead of passing control to the catch
below:
127.0.0.1/:1 Uncaught (in promise) {"stat": resp.status, "mesg": mesg.trim()}
Considering you are using fetch
you can also use async/await
and do the following. :
async function $get(url, callback) {
try {
const resp = await fetch(url, {credentials: "same-origin"});
if (!resp.ok) {
// this will end up in the catch statement below
throw({ stat: resp.status, mesg: (await resp.text()).trim());
}
callback({ stat: 200, data: await resp.json() });
} catch(error) {
callback(error);
}
}
I don't understand why you would use callback
functions though :) those are so 1999
To explain your error, you are calling resp.text()
twice when there is an error. To prevent that, you should immediately return the promise chained from the first resp.text()
call. This will also throw the error and end up in the catch block without reaching the consecutive then()
statement:
function $get(url, callback) {
fetch(url, {credentials: "same-origin"})
.then(resp => {
if (!resp.ok) {
return resp.text().then((mesg) => {
// ^^^^^^
throw {stat: resp.status, error: mesg.trim()}
});
}
return resp.json() ;
})
.then(data => callback({stat: 200, data }))
.catch(error => callback(error))
}
A "proper" $get
function which doesn't use callbacks:
function $get(url) {
return fetch(url, {credentials: "same-origin"}).then(async (resp) => {
const stat = resp.status;
if (!resp.ok) {
throw({ stat, error: (await resp.text()).trim() });
}
return { stat, data: await resp.json() };
});
}
Which you can consume like:
$get('https://example.com')
.then(({ stat, data }) => {
})
.catch({ stat, error}) => {
})