I'm using Promise.allSettled to call an array of URLs and I need to capture the response code of the request(s) of the rejected promise(s). Using the value of result.reason provided by Promise.allSettled is not accurate enough to assess the reason of rejection of the promise. I need the request response code (400, 500, 429, etc.).
I have so far the below:
var response = await Promise.allSettled(urls.map(url => fetch(url)))
.then(results => {
var data = [];
results.forEach((result, num) => {
var item = {
'req_url': urls[num],
'result': result.status,
'result_details': result
};
data.push(item);
});
return data;
});
How could I capture the response code of the request of the rejected promise and add it as a property within the returned array? The returned array should look ideally like this:
[{
'req_url': 'https://myurl.xyz/a',
'req_status_code': 400,
'result': 'rejected',
'result_details': {
'status': 'rejected',
'message': 'TypeError: Failed to fetch at <anonymous>:1:876'
}
},
{
'req_url': 'https://myurl.xyz/b',
'req_status_code': 419,
'result': 'rejected',
'result_details': {
'status': 'rejected',
'message': 'TypeError: Failed to fetch at <anonymous>:1:890'
}
},
{
'req_url': 'https://myurl.xyz/c',
'req_status_code': 429,
'result': 'rejected',
'result_details': {
'status': 'rejected',
'message': 'TypeError: Failed to fetch at <anonymous>:1:925'
}
}]
Any ideas?
fetch doesn't reject its promise on HTTP failure, only network failure. (An API footgun in my view, which I wrote up a few years back on my anemic old blog.) I usually address this by wrapping fetch in something that does reject on HTTP failure. You could do that as well, and make the failure status available on the rejection reason. (But you don't have to, see further below.)
class FetchError extends Error {
constructor(status) {
super(`HTTP error ${status}`);
this.status = status;
}
}
async function goFetch(url, init) {
const response = await fetch(url, init);
if (!response.ok) {
// HTTP error
throw new FetchError(response.status);
}
return response;
}
Then you could pass an async function into map to handle errors locally, and use Promise.all (just because doing it all in one place is simpler than doing it in two places with Promise.allSettled):
const results = await Promise.all(urls.map(async url => {
try {
const response = await goFetch(url);
// ...you might read the response body here via `text()` or `json()`, etc...
return {
req_url: url,
result: "fulfilled",
result_details: /*...you might use the response body here...*/,
};
} catch (error) {
return {
req_url: url,
result: "rejected",
result_status: error.status, // Will be `undefined` if not an HTTP error
message: error.message,
};
}
}));
Or you can do it without a fetch wrapper:
const results = await Promise.all(urls.map(async url => {
try {
const response = await fetch(url);
if (!response.ok) {
// Local throw; if it weren't, I'd use Error or a subclass
throw {status: response.status, message: `HTTP error ${response.status}`};
}
// ...you might read the response body here via `text()` or `json()`, etc...
return {
req_url: url,
result: "fulfilled",
result_details: /*...you might use the response body here...*/,
};
} catch (error) {
return {
req_url: url,
result: "rejected",
result_status: error.status, // Will be `undefined` if not an HTTP error
message: error.message,
};
}
}));
Related
I am making a GET request to an API that returns JSON. The response is usually JSON with a status 200. However, in rare cases it returns a status 300 with JSON. That 300 response gets caught in my React app as a ResponseError.
I am able to see the contents of the body in the network tab, and console.log(e.response.body) shows that it's a ReadableStream, but e.response.body.json() throws a TypeError.
The 200 status returns an object like:
{
user: { ... }
}
The 300 status returns an object like the following that can only be seen in the network tab:
{
users: {
user1: { ... },
user2: { ... }
}
How can I access the contents of the response body in my React app? The GET is made with fetch. Here's a simple example of what's happening:
try {
const res = await fetch('/api/user')
const data = await res.json()
// status 200 always works
} catch(e) {
// 300 status always goes here
// e.response.body is a ReadableStream
const await res = e.response.body.json() // throws a TypeError
}
In the catch block, I tried e.response.body.json(). That throws a TypeError.
You can avoid try catch block and use fetch API only as
fetch('/api/user')
.then((response) => {
if(response.status == 300 || response.status == 200){
return res.json()
}
})
.then((data) => console.log(data))
.catch((error) => {
console.error('Error:', error);
});
Hello: i am trying to do a fetch to an endpoint in a react component. The fetch i have is working (all console logs show it is working). since this a function so i can reuse it - i want to return something from it. i tried returning the response but i dont think i have my return in the right place or something else is wrong. here is the log i am referring to:
console.log(response)
and here is the full code:
const doFetch = (fetchUri) => {
fetch(fetchUri)
.then(async response => {
const data = await response.json();
// check for error response
if (!response.ok) {
// get error message from body or default to response statusText
const error = (data && data.message) || response.statusText;
return Promise.reject(error);
}
console.log('running fetch')
console.log(response)
console.log('showing data from fetch')
console.log(data)
return(response)
// this.setState({ totalReactPackages: data.total })
})
.catch(error => {
this.setState({ errorMessage: error.toString() });
console.error('There was an error!', error);
});
};
So I am hoping someone can help me do that 'return' properly. Thanks! Gerard
you did returned properly inside fetch but you did not returned fetch itself :)
const doFetch = (fetchUri) => {
return fetch(fetchUri)
.then(async response => {
const data = await response.json();
// check for error response
if (!response.ok) {
// get error message from body or default to response statusText
const error = (data && data.message) || response.statusText;
return Promise.reject(error);
}
console.log('running fetch')
console.log(response)
console.log('showing data from fetch')
console.log(data)
return (response)
// this.setState({ totalReactPackages: data.total })
})
.catch(error => {
this.setState({errorMessage: error.toString()});
console.error('There was an error!', error);
});
};
doFetch('randomurl').then(parsedAndPreparedResponse => this.setState({ data: parsedAndPreparedResponse }))
As Michal pointed out in his answer, you're only returning within the fetch, not from doFetch itself.
But to extend a little
The async/await within the then() is unnecessary since no asynchronous call is being made within it. The callback in then() will be run when the fetch promise is resolved.
So either get rid of the async/await or change your code to only use async/await.
If you do it could look something like this:
const doFetch = async fetchUri => {
try {
const response = await fetch(fetchUri)
// check for error response
if (!response.ok) {
throw new Error({
status: response.status,
statusText: response.statusText
});
}
// On successful call
const data = response.json();
// Return the data
return data();
} catch (error) {
// Error handling
}
};
You can call the function like this:
// Using the await keyword
const response = await doFetch(/* your fetchUri */);
// Using .then()
// The callback function in then() will run when the promise from
// doFetch() is resolved
doFetch(/* your fetchUri */).then(res => {
// Whatever you want to do with the response
});
Remember that in order you use the await keyword you need to be inside an async function.
const anAsyncFunction = async () => {
const response = await doFetch(/* your fetchUri */);
}
I am doing a simple api get request, but I can't seem to isolate the array on its own. its always inside of a promise and I'm not sure how to remove it or how to access the values stored in the array.
function getLocation(name) {
let output = fetch(`http://dataservice.accuweather.com/locations/v1/cities/search?apikey=oqAor7Al7Fkcj7AudulUkk5WGoySmEu7&q=london`).then(data => data.json());
return output
}
function App() {
var output = getLocation(`london`);
console.log (output)
...
__proto__: Promise
[[PromiseStatus]]: "resolved"
[[PromiseValue]]: Array(3)
is what is displayed in the console.log I require just the Array(3)
fetch, and Promise#then, always return a promise. To access the information it fetches, consume the promise:
getLocation()
.then(data => {
// use the data
})
.catch(error => {
// Handle/report the error
});
or in an async function:
const data = await getLocation();
Side note: Your getLocation has a very common error (so common I wrote it up on my anemic little blog): It doesn't check that the HTTP operation succeeded. fetch only fails on network errors, not HTTP errors. To fix it:
function getLocation(name) {
return fetch(`http://dataservice.accuweather.com/locations/v1/cities/search?apikey=oqAor7Al7Fkcj7AudulUkk5WGoySmEu7&q=london`)
.then(response => {
if (!response.ok) {
throw new Error("HTTP error, status = " + response.status);
}
return response.json();
});
}
I'm trying for my application to wait for the promise to return before executing other code, which is dependant on that data. For this I am using then(), but it is not working as expected, as the next code is still being executed, before my values are being returned.
I am using Express to handle requests and Axios to make my own requests.
index.js:
app.get('/api/guild/:serverId', async (req,res) => {
bot.getGuild(req.params.serverId).then((response) => { // It should here wait for the promise before executing res.send(...)...
res.send(response.data);
}).catch((error) => {
console.log(error) // Error: Returns that response is undefined
});
});
bot.js:
module.exports.getGuild = async function (id){
axios.get(BASE_URL + `guilds/${id}`, {
headers: {
'Authorization' : 'Bot ' + token // Send authorization header
}
}).then(function (response){ // Wait for response
console.log("Returning guild data")
return response; // Sending the response and also logging
}).catch(function (error){
console.log("Returning undefined.")
return undefined; // This is not being used in my problem
});
}
I already know that getGuild(id) is returning a working response. It also logs Returning guild data when returning the data. Yet this is being returned after index.js returning the error, that the response is undefined. Even though it should actually wait for the Promise to be fulfilled and then working with response.data.
Log:
TypeError: Cannot read property 'data' of undefined
at bot.getGuild.then (...\website\src\server\index.js:47:27)
at process._tickCallback (internal/process/next_tick.js:68:7)
Returning guild data
then is not needed in async functions because await is syntactic sugar for then.
getGuild doesn't return a promise from Axios, so it cannot be chained.
It should be:
module.exports.getGuild = function (id){
return axios.get(BASE_URL + `guilds/${id}`, {
...
The use of catch in getGuild is a bad practice because it suppresses an error and prevents it from being handled in caller function.
The getGuild function must wait for the axios promise in order to return a result:
try {
let res = await axios.get(BASE_URL + `guilds/${id}`, {
headers: {
'Authorization': 'Bot ' + token // Send authorization header
}
})
console.log("Returning guild data")
return res
} catch (exp) {
console.log("Returning undefined.")
return undefined;
}
I have an HTTP API that returns JSON data both on success and on failure.
An example failure would look like this:
~ ◆ http get http://localhost:5000/api/isbn/2266202022
HTTP/1.1 400 BAD REQUEST
Content-Length: 171
Content-Type: application/json
Server: TornadoServer/4.0
{
"message": "There was an issue with at least some of the supplied values.",
"payload": {
"isbn": "Could not find match for ISBN."
},
"type": "validation"
}
What I want to achieve in my JavaScript code is something like this:
fetch(url)
.then((resp) => {
if (resp.status >= 200 && resp.status < 300) {
return resp.json();
} else {
// This does not work, since the Promise returned by `json()` is never fulfilled
return Promise.reject(resp.json());
}
})
.catch((error) => {
// Do something with the error object
}
// This does not work, since the Promise returned by `json()` is never fulfilled
return Promise.reject(resp.json());
Well, the resp.json promise will be fulfilled, only Promise.reject doesn't wait for it and immediately rejects with a promise.
I'll assume that you rather want to do the following:
fetch(url).then((resp) => {
let json = resp.json(); // there's always a body
if (resp.status >= 200 && resp.status < 300) {
return json;
} else {
return json.then(Promise.reject.bind(Promise));
}
})
(or, written explicitly)
return json.then(err => {throw err;});
Here's a somewhat cleaner approach that relies on response.ok and makes use of the underlying JSON data instead of the Promise returned by .json().
function myFetchWrapper(url) {
return fetch(url).then(response => {
return response.json().then(json => {
return response.ok ? json : Promise.reject(json);
});
});
}
// This should trigger the .then() with the JSON response,
// since the response is an HTTP 200.
myFetchWrapper('http://api.openweathermap.org/data/2.5/weather?q=Brooklyn,NY').then(console.log.bind(console));
// This should trigger the .catch() with the JSON response,
// since the response is an HTTP 400.
myFetchWrapper('https://content.googleapis.com/youtube/v3/search').catch(console.warn.bind(console));
The solution above from Jeff Posnick is my favourite way of doing it, but the nesting is pretty ugly.
With the newer async/await syntax we can do it in a more synchronous looking way, without the ugly nesting that can quickly become confusing.
async function myFetchWrapper(url) {
const response = await fetch(url);
const json = await response.json();
return response.ok ? json : Promise.reject(json);
}
This works because, an async function always returns a promise and once we have the JSON we can then decide how to return it based on the response status (using response.ok).
You would error handle the same way as you would in Jeff's answer, however you could also use try/catch, an error handling higher order function, or with some modification to prevent the promise rejecting you can use my favourite technique that ensures error handling is enforced as part of the developer experience.
const url = 'http://api.openweathermap.org/data/2.5/weather?q=Brooklyn,NY'
// Example with Promises
myFetchWrapper(url)
.then((res) => ...)
.catch((err) => ...);
// Example with try/catch (presuming wrapped in an async function)
try {
const data = await myFetchWrapper(url);
...
} catch (err) {
throw new Error(err.message);
}
Also worth reading MDN - Checking that the fetch was successful for why we have to do this, essentially a fetch request only rejects with network errors, getting a 404 is not a network error.
I found my solution at MDN:
function fetchAndDecode(url) {
return fetch(url).then(response => {
if(!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
return response.blob();
}
})
}
let coffee = fetchAndDecode('coffee.jpg');
let tea = fetchAndDecode('tea.jpg');
Promise.any([coffee, tea]).then(value => {
let objectURL = URL.createObjectURL(value);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
})
.catch(e => {
console.log(e.message);
});
Maybe this option can be valid
new Promise((resolve, reject) => {
fetch(url)
.then(async (response) => {
const data = await response.json();
return { statusCode: response.status, body: data };
})
.then((response) => {
if (response.statusCode >= 200 && response.statusCode < 300) {
resolve(response.body);
} else {
reject(response.body);
}
})
});