I am struggling to extract data from an API call. I need it to return an array of keys but I keep getting [object Promise] when I try to render.
const apiKey = '*****';
const container = document.getElementById('root');
const Yelp = {
async search(term, location, searchBy) {
let response = await fetch(`https://cors-anywhere.herokuapp.com/https://api.yelp.com/v3/businesses/search?term=${term}&location=${location}&sort_by=${searchBy}`, {
headers: {
Authorization: `Bearer ${apiKey}`
}
})
if (response.ok) {
let jsonResponse = await response.json()
return jsonResponse.businesses.map(business => {
return business
})
}
}
}
console.log(Yelp.search('pasta', 'london', 'best_match'));
Since the Yelp.search function is async, it will always return a promise. That's the reason you observe a promise when you log the immediate result after calling it. Instead, you should await the promise like:
Yelp.search('pasta', 'london', 'best_match').then(results => {
console.log(results)
})
So to answer your question, you would call the then() method of the Promise to wait for its results to resolve. The then method takes two arguments. The first is a function that you provide to handle the results of the promise. The second is a function that you provide to handle any errors returned by the promise. For example:
Yelp.search('pasta', 'london', 'best_match').then(results => {
// handle the results
console.log(results)
}, err => {
// handle the error
console.error(err)
})
You may also catch exceptions thrown by the promise by invoking catch(), like so:
Yelp.search('pasta', 'london', 'best_match')
.then(results => console.log(results), err => console.error(err))
.catch(ex => console.error(ex))
Related
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 wast trying to fetch some data in componentDidMount and then set it to state, wrote an async await function for the same. But if i was trying to set the state with the values right after await, the value was getting set to a pending promise but console logging would give the correct output.
For that reason i called the getData().then() to set the data, why it was giving pending promise can someone clear the concept out here?
componentDidMount() {
async function getData() {
const baseUrl = `https://........`;
const response = await fetch(baseUrl);
if (response.status === 200) {
const json = await response.json();
const { data } = json;
//console.log(data) =>correct output
return data;
}
return null;
}
getData().then(data => {
this.setState({ names: data });
});
}
You can simply do it like that:
componentDidMount() {
const baseUrl = `https://........`;
fetch(baseUrl)
.then((response) => response.json())
.then(result => {
this.setState({ names: result.data});
});
}
getData is an async function that you syncronize your fetch call inside. So it returns a promise. You're console logging inside that function after response fetched. So it logs the data.
You can think it as Fetch function that you use, you're awaiting it because it's a async function and you should await for the response. It's the same for the getData too. No matter how you wait, use then or await.
I have the following setup where I am trying to make a call to a function which returns a Promise, which in turn has a Promise.all() call to make an indefinite number of axios.post() calls. I tried using axios.all() and axios.spread(...responses), but no luck. I've also tried a number of examples posted to SO from experienced users. But still no luck.
It's worth noting that the calls are being made, it's just that the responses being returned are undefined.
The function being called is the following:
createSurveyResult(surveyResults) {
let token = Vue.prototype.$auth.getLocalStorage(`${this.activeUser.uuid)_{process.env.NODE_ENV}_ACCESS_TOKEN`)
if (token) {
axiosModified.defaults.headers.common['Authorization'] = `Bearer ${token}`
}
return new Promise(resolve => {
let promiseArray = surveyResults.map(payload => {
axiosModified.post('/feedback/results/', payload)
})
Promise.all(promiseArray).then(responses => {
responses.forEach(res => console.log(res))
console.log('submitted all axios calls');
resolve(responses)
}).catch(error => {
resolve(error.response)
})
})
}
Which is being called as:
this.$surveys.createSurveyResult(surveyResults).then((responses) => {
console.log(responses)
});
Yet, both the console.log(res) res objects return are undefined within the Promise.all() , the console.log('submitted all axios calls'); is being called, and the responses from the then() callback are returned as:
I'm wondering what I am doing wrong here?
Write your function like this:
createSurveyResult(surveyResults) {
let token = Vue.prototype.$auth.getLocalStorage(
`${this.activeUser.uuid}_${process.env.NODE_ENV}_${ACCESS_TOKEN}`
);
if (token) {
axiosModified.defaults.headers.common["Authorization"] = `Bearer ${token}`;
}
return Promise.all(
surveyResults.map((payload) =>
axiosModified.post("/feedback/results/", payload)
)
);
}
Consider the following Javascript/React code:
// Javascript function that has a fetch call in it.
export const signIn = (email:string, password:string) => {
console.log("FETCHING...");
fetch(`${endPoint}/sign_in`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
password
})
})
.then((response) => {
return response.json()
})
.then(({ data }) => {
console.log("FETCHED DATA...")
})
.catch((error) => {
console.error('ERROR: ', error)
})
console.log("DONE FETCHING...");
}
// A functional component that references signIn.
export const SignIn: React.FC<Props> = () => {
// irrelevant code ...
const onSubmit = (e: CustomFormEvent) => {
e.preventDefault()
console.log("SIGNING IN...")
// calls my signIn function from above
// I don't want this to finish until the fetch inside it does.
signIn(email, password, setAuthentication, setCurrentUser)
console.log("SIGNED IN...");
}
return <>A form here submits and calls onSubmit</>
}
This produces the following console log output:
SIGNING IN...
FETCHING...
DONE FETCHING...
SIGNED IN...
FETCHED DATA...
I want FETCHED DATA... to show up before DONE FETCHING.... I've tried playing around with aysnc/await but that's not working so I don't know where to go from here.
Just add another .then
.then((response) => {
return response.json()
})
.then(({ data }) => {
console.log("FETCHED DATA...")
return
}).then(()=> {
console.log("DONE FETCHING...");
})
.catch((error) => {
console.error('ERROR: ', error)
})
It would have to be in the then statements if you want the console.log to wait until the promise is resolved. Here's an example that uses async/await:
export const signIn = async (email:string, password:string) => {
console.log("FETCHING...");
const response = await fetch(`${endPoint}/sign_in`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
password
})
})
const data = await response.json();
console.log("FETCHED DATA...")
console.log("DONE FETCHING...");
}
You would also need to turn this into an async function if you want the console.log to happen after the data is done fetching:
const onSubmit = async (e: CustomFormEvent) => {
e.preventDefault()
console.log("SIGNING IN...")
// calls my signIn function from above
// I don't want this to finish until the fetch inside it does.
await signIn(email, password, setAuthentication, setCurrentUser)
console.log("SIGNED IN...");
}
In order to use async await, you need to return a promise from the call. So basically you don't execute the .then and wrap the call in a try catch block.
export const signIn = async (email:string, password:string) => {
console.log("FETCHING...");
return fetch(`${endPoint}/sign_in`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
password
})
})
}
and
const onSubmit = async (e: CustomFormEvent) => {
e.preventDefault()
console.log("SIGNING IN...")
// calls my signIn function from above
// I don't want this to finish until the fetch inside it does.
try {
const data = await signIn(email, password, setAuthentication, setCurrentUser)
// Parse data, do something with it.
console.log("SIGNED IN...");
} catch (e) {
// handle exception
}
}
You may want to look more into how promises in JavaScript works.
One problem here is in signIn. What you're doing right now is:
function signIn() {
// 1. log FETCHING
// 2. call asynchronous fetch function
// 3. log DONE FETCHING
}
The key here is that fetch is asynchronous. The program doesn't wait for it to finish before moving on. See the problem? The JavaScript interpreter is going to run step 3 without waiting for step 2 to finish.
There are multiple ways to fix this. First, you can use then. Here's an example:
promise
.then(res => func1(res))
.then(res => func2(res))
.then(res => func3(res))
Here, you're telling JavaScript to:
Run promise, and wait for it to resolve.
Take the result from promise and pass it into func1. Wait for func1 to resolve.
Take the result from func1 and pass it into func2. Wait for func2 to resolve.
etc.
The key difference here is that you are running each then block in order, waiting for each previous promise to be resolved before going to the next one. (Whereas before you didn't wait for the promise to resolve).
Your code with promises would look like:
export const signIn = (email: string, password: string) => {
console.log("FETCHING...")
// Note that we return the promise here. You will need this to get onSubmit working.
return fetch(/* args */)
.then(res => res.json())
.then(({ data }) => console.log("DONE FETCHING"))
.catch(err => /* HANDLE ERROR */)
}
The second way to fix this is to use async and await. async and await is simply syntax sugar over promises. What it does underneath is the exact same, so make sure you understand how promises work first. Here's your code with async and await:
// The async keyword here is important (you need it for await)
export const signIn = async (email: string, password: string) => {
console.log("FETCHING...");
try {
const res = await fetch(/* args */) // WAIT for fetch to finish
const { data } = res.json()
console.log("FETCHED DATA...")
} catch (err) {
/* HANDLE ERROR */
}
console.log("DONE FETCHING...")
}
There's also a second similar problem in onSubmit. The idea is the same; I'll let you figure it out yourself (the important part is that you must return a Promise from signIn).
Is this the only way to use the body.json() and also get the status code?
let status;
return fetch(url)
.then((response => {
status = response.status;
return response.json()
})
.then(response => {
return {
response: response,
status: status
}
});
This doesn't work as it returns a promise in the response field:
.then((response)=> {return {response: response.json(), status: response.status}})
Your status is not visible in the second then. You can just get the two properties in the single then.
json() returns a new Promise to you, so you need to create your object inside the then of the result of that function. If you return a Promise from a function, it will be fulfilled and will return the result of the fulfillment - in our case the object.
fetch("https://jsonplaceholder.typicode.com/posts/1")
.then(r => r.json().then(data => ({status: r.status, body: data})))
.then(obj => console.log(obj));
The .json method returns a promise, not the parsed value itself. If you want to access both the response and the parsed value in the same callback, you'll need to use nested functions like this:
fetch(url)
.then(response => {
response.json().then(parsedValue => {
// code that can access both here
})
});
Alternatively, you can use await inside an asynchronous function to eliminate the need for callbacks.
const response = await fetch(url);
const parsedValue = await response.json();
// code that can access both here
Of course, you'll want to check for errors, either with a .catch(...) call on a Promise or with a try...catch block in an async function. You could make a function that handles JSON and error cases, and then reuse it for all fetches. For example, something like this:
function handle(response) {
if (response.ok) {
return response.json().then(parsedValue => {
// the status was ok and the body could be parsed
return Promise.resolve({ response, parsedValue });
}).catch(err => {
// the status was ok but the body was empty or not JSON
return Promise.resolve({ response });
});
} else {
return response.json().catch(err => {
// the status was not ok and the body was unobtainable/empty/not JSON
throw new Error(response.statusText);
}).then(parsedValue => {
// the status was not ok and the body was JSON
throw new Error(parsedValue.error.message); // assuming an error message is returned by our REST API
});
}
}
I don't think it's the best design pattern, but hopefully this clarifies how the fetch API works.
PS: I avoided naming any variable or property json since that is the name of the text format. Once it's been parsed, it is no longer JSON.
Using two 'then's seem unnecessary to me.
async/await could get the job done pretty easily.
fetch('http://test.com/getData')
.then( async (response) => {
// get json response here
let data = await response.json();
if(data.status === 200){
// Process data here
}else{
// Rest of status codes (400,500,303), can be handled here appropriately
}
})
.catch((err) => {
console.log(err);
})
Did you try this?
return fetch(url)
.then((r)=> {return {response: r.json(), status: r.status}})
I think the cleanest way is to create a Promise.all() with the pieces you need.
.then(response => Promise.all([Promise.resolve(response.ok), response.text()]))
Which can be written shorter as
.then(response => Promise.all([response.ok, response.text()]))
The promise returns an array with all of the results
.then(data => ({ status: data[0], response: data[1] }))