I have an API that includes a useful description of what went wrong when an error is raised by the server (status = 500). The description comes as part of the response text. My client code, using Aurelia, calls the api via aurelia-fetch-client using a generic method to make the call:
function callRemoteService(apiName, timeout) {
return Promise.race([
this.http.fetch(apiName),
this.waitForServer(timeout || 5000) // throws after x ms
])
.then(response => response.json() )
.catch(err => {
if (err instanceof Response) {
// HERE'S THE PROBLEM.....
err.text().then(text => {
console.log('Error text from callRemoteService() error handler: ' + text);
throw new Error(text)
});
} else if (err instanceof Error) {
throw new Error(err.message);
} else {
throw new Error('Unknown error encountered from callRemoteService()');
}
});
}
Note that I want to catch the server (fetch or timeout) errors in a consistent way, and then throw back just a simple error message to the calling view. I can invoke callRemoteService successfully, catching errors when a 500 is returned with:
callRemoteService(this.apiName, this.apiTimeout)
.then(data => {
console.log('Successfully called \'' + this.apiName +
'\'! Result is:\n' + JSON.stringify(data, null, 2));
})
.catch(err => {
console.log('Error from \'' + this.apiName + '\':',err)
});
However, I'm having trouble accessing the response text, because the fetch provides the text() method that returns a promise, and that's interfering with my otherwise happy promise chaining. The code above doesn't work, leaving me with an Uncaught (in promise) error.
Hopefully there's a good way to access that response text?
This should do the trick:
function callRemoteService(apiName, timeout = 5000) {
return Promise.race([
this.http.fetch(apiName)
.then(
r => r.json(),
r => r.text().then(text => throw new Error(text))
),
this.waitForServer(timeout)
]);
}
by the way, I like what you're doing with Promise.race- nice technique!
Related
I would like to do some error handling on the response received from a call I am making and then move to the catch if the specific null check is hit. Something like this:
fetch('example.json')
.then(response => {
if (response.data === null) {
//go to catch
}
})
.catch(error => {
console.error("error happened", error);
})
What would be the best way to go about doing something like this? Any red flags with throwing an error inside a then block?
If you throw in a promise handler, that rejects the promise the handler returns. So:
fetch('example.json')
.then(response => {
if (response.data === null) {
throw new Error();
}
})
.catch(error => {
console.error("error happened", error);
})
What you throw will be the rejection reason the catch handler sees. It doesn't have to be an Error, but as with synchronous code, it's generally best if it is.
But, note that A) A fetch response doesn't have a data property, and B) You need to check for HTTP success and parse the JSON that was returned.
You probably want something like this:
fetch('example.json')
.then(response => {
if (!response.ok) {
// (I tend to use an specialized `Error` type here
// More on my anemic blog:
// http://blog.niftysnippets.org/2018/06/common-fetch-errors.html)
throw new Error("HTTP error " + response.status);
}
return response.json();
})
.then(data => {
if (data === null) {
throw new Error("The data is null");
})
// ...do something with `data`...
})
.catch(error => {
console.error("error happened", error);
});
In a comment on the question you've said:
i was hoping there was a way to check this response object without having to trigger the 'extreme' measure of throwing an exception
You do have an alternative which is basically identical in outcome: Return a rejected promise. Here's my second code block above adapted to do that:
fetch('example.json')
.then(response => {
if (!response.ok) {
// (I tend to use an specialized `Error` type here
// More on my anemic blog:
// http://blog.niftysnippets.org/2018/06/common-fetch-errors.html)
return Promise.reject(new Error("HTTP error " + response.status));
}
return response.json();
})
.then(data => {
if (data === null) {
return Promise.reject(new Error("The data is null"));
})
// ...do something with `data`...
})
.catch(error => {
console.error("error happened", error);
});
And as with the throw version, you don't have to use an Error, it's just best practice. It can be anything you want.
If you want, you can throw an Error object from within your promise handler.
fetch('example.json')
.then(response => {
if (response.data === null) {
throw new Error('oopsie');
}
})
.catch(error => {
console.error("error happened", error); // will show "Error: oopsie"
})
Below is code for fetch in javascript:
fetch(url + "?" + o)
.then(response => {
if (response.ok) {
resolve(response.text());
} else {
reject(response.json()); ///issue in read json data how to read json?
}
})
.then(html => {
debugger
document.getElementById('partialresult').innerHTML = html;
})
.catch(err => {
debugger
console.log("Can’t access " + url + " response. Blocked by browser?" + err)
´
document.getElementById('partialresult').innerHTML = err;
});
I need to read json if (!response.ok) i need to read the json data in catch or any where just div need to updated..
Return Json format:
{ success = false, message = "Operation failed." }
How to read json in fetch?
EDIT : server return succees in html and failure( error) in json ..html working fine and i need parse json data if failure case show in div
In the code you've shown, you're trying to use resolve and reject identifiers that haven't been declared anywhere (that you've shown).
To settle the promise returned by then from within its callback, use return to return a value (or a promise to resolve to) or throw (or a rejected promise).
In a comment you've said:
Actually server return succees in html and failure( error) in json ..i need parse json data if failure case show in div
To handle that, I think I'd convert the server's error object into an Error and throw it; see comments:
fetch(url + "?" + o)
.then(response => {
if (response.ok) {
// Read the HTML that comes with success
return response.text();
} else {
// Read the JSON that comes with the error,
// then use it as an error
return response.json()
.then(serverError => {
throw new Error(serverError.message);
});
}
})
.then(html => {
// Here, you have the HTML result
document.getElementById('partialresult').innerHTML = html;
})
.catch(err => {
// Here you have either a network error or an error
// we caught above and used to create an Error instance
document.getElementById('partialresult').innerHTML = err.message || String(err);
});
so I have a chain of request that are sent, and once catch at the end, the problem is if I have an error i wanna retry that specific request that caught the error, I know one solution to this would be to add a catch at the end off all the request i send, and when it catches an error it retries that request, but that would lead to too many catch statements, I just want one catch statement at the end that when it catches an error it retrys the specific request
rp.get('https://www.off---white.com/en-us/api/products/' + variant, options2)
.then((data) => {
// doo stuff with request
return rp.post('https://www.off---white.com/en-us/api/bags/' + bagId + '/items', options2)
})
.then((data) => {
// doo stuff with request
})
.catch((error) => {
})
Your example indicates that some requests depend on the response of a previous request. Adding a catch handler at the end of the promise chain would make it extremely difficult to retry the request and continue with subsequent requests. You need to handle the error at the request, not at the end of the promise chain. This is pretty simple to do if you wrap up the request in a helper method.
function request(opts) {
return rp(opts).catch(() => request(opts));
}
request({url: 'https://www.off---white.com/en-us/api/products/' + variant, ...options2})
.then((data) => {
// doo stuff with request
return request({method: 'POST', url: 'https://www.off---white.com/en-us/api/bags/' + bagId + '/items', ...options2});
})
.then((data) => {
// doo stuff with request
})
.catch((error) => {
});
It's not really clear what options2 is and why you use it as the request body in the second request, so this may not work exactly as you would expect, but the parameters passed into request can be tweaked to fit your use case. This will also result in an infinite request loop if the request always fails, you should implement some basic error handling to avoid this infinite loop (e.g., only retry X number of times, or retry only when you get a specific error, etc.)
First of all, you will need to design your rp.get() function in a way such that somehow whenever there is an error, the error object must have an identifier (stored as type key) as of from which request the error is propagated, then on a conditional basis, you can handle the error in a single catch() block accordingly.
const rpWrapper = (type) => { //add other required params
rp.get() //pass required params
.then( (res) => result )
.catch( (err) => { throw { type, err }) }
}
Then, modify your original code to something like,
rpWrapper.get('https://www.off---white.com/en-us/api/products/' + variant, options2)
.then((data) => {
// doo stuff with request
return rpWrapper.post('https://www.off---white.com/en-us/api/bags/' + bagId + '/items', options2)
})
.then((data) => {
// doo stuff with request
})
.catch((error) => {
if(error.type === "request1") { //handle error on first req }
if(error.type === "request2") { //handle error second req }
})
The following code works well and logs to the console a fetch from a website (that outputs a simple file already in json format):
getData = url => {
fetch(url)
.then(response => {
if (response.status !== 200) {
console.log(
"Looks like there was a problem. Status Code: " + response.status
);
return; //returns undefined!
}
// Examine the text in the response
response.json().then(data => {
console.log(data);
});
})
.catch(function(err) {
console.log("Fetch Error :-S", err);
});
};
getData(urlToFetch); // logs to console the website call: a json file
I want to store that fetch's content values in a variable for later use.
So, when I change:
console.log(data);
to:
return data;
I get an undefined. Any help?
Because you .catch in your getData function if something else goes wrong your function will resolve undefined as well. If you want to log it then you need to return the error as a rejecting promise so the caller can handle the error and not get an undefined value for data when the promise resolves.
You can return Promise.reject("no 200 status code") for rejecting and return response.json() for resolve If you want to add .then(x=>console.log(x)) you still need to return something or the thing calling getData will resolve to undefined:
getData = url => {
fetch(url)
.then(response => {
if (response.status !== 200) {
console.log(
"Looks like there was a problem. Status Code: " + response.status
);
return Promise.reject(//reject by returning a rejecting promise
"Looks like there was a problem. Status Code: " + response.status
);
}
// Examine the text in the response
response.json().then(data => {
console.log(data);
return data;//still need to return the data here
});
})
.catch(function (err) {
console.log("Fetch Error :-S", err);
//return the reject again so the caller knows something went wrong
return Promise.reject(err);
});
};
getData(urlToFetch) // logs to console the website call: a json file
.then(
x=>console.log("caller got data:",x)
)
.catch(
e=>console.log("caller got error:",e)
);
You could use ES6 async-await to get this done easily:
Using async-await, your code will look like this:
function getData(url){
return new Promise((resolve, reject) => {
fetch(url)
.then(response => {
if (response.status !== 200) {
console.log(
"Looks like there was a problem. Status Code: " + response.status
);
return; //returns undefined!
}
// Examine the text in the response
response.json().then(data => {
resolve(data);
});
})
.catch(function(err) {
console.log("Fetch Error :-S", err);
reject(err)
});
})
}
// Then call the function like so:
async function useData(){
const data = await getData(urlToFetch);
// console.log(data) ===> result;
}
return; //returns undefined!
You aren't returning anything, so return by itself returns undefined unless you supply it with a value.
You need to store the promise and when you need a value, you will have to resolve it. I would change this:
// Examine the text in the response
response.json().then(data => {
console.log(data);
});
to this:
// Examine the text in the response
return response.json();
Then call getData and either resolve the promise:
getData(urlToFetch).then(data => {
// Code to use data
})
Or store the promise in the variable and use it later:
let result = getData(urlToFetch);
result.then(data => {
// so whatever with data
});
What happens here is, you are under then block. Your getData functions returns the data as soon as it call fetch. It doesn't wait for the async fetch opertion to receive callback and then call function inside then.
So the return inside then function have nothing to return to. That's why it doesn't work.
Instead you can return a promise from the getData function and call then on it to get the data. I faced a similar issue sometime ago.
An example: (not tested)
getData = url => {
new Promise(function (resolve, reject) {
fetch(url)
.then(response => {
if (response.status !== 200) {
console.log(
"Looks like there was a problem. Status Code: " + response.status
);
reject(); //returns undefined!
}
// Examine the text in the response
response.json().then(data => {
resolve(data);
});
})
.catch(function(err) {
console.log("Fetch Error :-S", err);
});
};
};
getData(urlToFetch).then(data => /*do something*/ ); // logs to console the website call: a json file
So when the data is available, you can call resolve(data) and it will invoke then method on your promise.
Hope it helps.
Currently, I'm using fetch with redux-thunk to read code from an API -
my code reads like this:
export function getUsers() {
return (dispatch) => {
// I have some helper code that automatically resolves the json promise
return fetch(`/users`)
.then((resp, json) => {
if (resp.status === 200) {
dispatch(getUsersSuccess(json));
} else {
dispatch(getUsersFail(json));
}
}).catch((err) => {
// network error
dispatch(getUsersFail(err));
});
};
}
The problem here is the catch method, as it will catch any error thrown in the then block. This commonly means that if some React component's render function fails with a programmer error, that error gets swallowed up back into dispatch(getUsersFail(err)).
Ideally, I'd like to detect if err is a fetch error (and dispatch my own action), otherwise throw. However, fetch throws a generic TypeError. How can I reliably detect that the error caught was one thrown by fetch?
Don't use .catch() but install the error handler directly on the fetch promise, as the second - onreject - argument to .then():
return fetch(`/users`)
.then(([resp, json]) => {
if (resp.status === 200) {
dispatch(getUsersSuccess(json));
} else {
dispatch(getUsersFail(json));
}
}, (err) => {
// network error
dispatch(getUsersFail(err));
});
Check out the difference between .then(…).catch(…) and .then(…, …) for details.
Btw, I'd recommend to write
return fetch(`/users`)
.then(([resp, json]) => resp.status === 200 ? getUsersSuccess(json) : getUsersFail(json)
, getUsersFail)
.then(dispatch);