This question already has answers here:
How to return value from an asynchronous callback function? [duplicate]
(3 answers)
Closed 6 months ago.
function foo() {
let url = `https://foobar.com`
fetch(url, {
method: 'POST',
headers: headers
}).then(response => {
return response.json();
}).then(data => {
return data['jobs'];
}).then(jobs => {
let successful = [];
jobs.forEach(job => {
let jobUrl = job['url'];
fetch(jobUrl, {
headers: headers
}).then(response => {
return response.json();
}).then(data => {
let job_status = data['status'];
let job_timeStamp = data['timestamp'];
if (job_status === 'SUCCESS') {
console.log(job_status, job_timeStamp);
successful.push({jobUrl:jobUrl, timeStamp:job_timeStamp});
}
})
});
console.log(successful);
})
}
foo()
When I am running the above code, successful array is not getting populated. How can I populate the successful array? I should be able to sort that array later based on timestamp.
The issue with your code is that fetch is asynchronous, so none of the fetches will have run yet when you console.log(successful)
Here's two alternatives, using async / await since that will simplify the code immensely - I've also used a liberal amount of destructuring sugar
If all the jobUrl fetches can be done in parallel - use this
async function foo() {
// function to get one jobUrl data
// called by map later on
const getJob = async ({url:jobUrl}) => {
const response = await fetch(jobUrl, {headers});
const {status, timestamp: timeStamp} = await response.json();
if (status === 'SUCCESS') {
return {jobUrl, timeStamp};
}
};
let url = `https://foobar.com`;
const response = await fetch(url, {method: 'POST', headers});
const {jobs} = await response.json();
const unfiltered = await Promise.all(jobs.map(getJob));
// this "unfiltered" array will have `undefined` items for when status !== SUCCESS
// so filter them out
const successful = unfiltered.filter(v => v);
// here you have successful populated and can use it
}
If the jobUrl fetches have to be done one by one - use this
async function foo() {
let url = `https://foobar.com`;
const response = await fetch(url, {method: 'POST', headers});
const {jobs} = await response.json();
const successful = [];
for ({url:jobUrl} of jobs) {
const response = await fetch(jobUrl, {headers});
const {status, timestamp: timeStamp} = await response.json();
if (status === 'SUCCESS') {
successful.push({jobUrl, timeStamp});
}
};
// here you have successful populated and can use it
}
Related
This question already has answers here:
How to return many Promises and wait for them all before doing other stuff
(6 answers)
Closed 4 months ago.
i created a function named getCartItems which calls getSingleItems passing id as argument. if i log the json result in getSingleItem it is showing object of the product correctly but when i try to access the function call value i get a promise how to resolve it?
const getSingleItem = async (id)=>{
const response = await fetch("http://localhost:4000/products/"+id, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
})
const json = await response.json();
return json;
}
const getCartItems = () => {
let currArr = JSON.parse(localStorage.getItem('cart'));
let newArr = currArr.map( async (el)=>await getSingleItem(el.id))
console.log(newArr);
setCartItems(newArr);
}
useEffect(()=>{
getCartItems();
}, [])
if try to use for loop instead of map it shows promise pending and throws connection error.
You need to resolve the promises from the map method with Promise.all and await for it before setting the cartItems state.
const getSingleItem = async (id)=>{
const response = await fetch("http://localhost:4000/products/"+id, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
})
const json = await response.json();
return json;
}
const getCartItems = async () => {
let currArr = JSON.parse(localStorage.getItem('cart'));
let newArr = await Promise.all(currArr.map( async (el)=>await getSingleItem(el.id)))
console.log(newArr);
setCartItems(newArr);
}
useEffect(()=>{
getCartItems();
}, [])
I would like to get http status code after response.json to use it in my logic later, can I do something with it?
function apiRequest(path, options) {
fetch("api/" + path, options)
.then(response => response.json())
.then(data => {
let res = {
code: 200 //I want to put http status code here,
data: data
}
return res;
})
}
This is slightly tricky using then (as you are currently doing) because you want to get data directly from the response (which is a promise) and some more data from the parsed body (which is another promise).
So you can wrap the status and the data promise in a Promise.all and return that from the first then:
const apiRequest = () => {
const url = "//swapi.dev/api/planets/1/";
fetch(url)
.then((response) => Promise.all([response.status, response.json()]))
.then(([status, data]) => console.log({status, data}))
}
… but it would be easier to use async/await syntax and ditch the callbacks and you then only have to worry about a single function (and therefore scope) rather than multiple.
const apiRequest = async () => {
const url = "//swapi.dev/api/planets/1/";
const response = await fetch(url);
const data = await response.json();
const status = response.status;
console.log({status, data})
}
As an alternative you could consider async/await. That way you have access to response and data at the same time more easily.
async function apiRequest(path, options) {
const response = await fetch("api/" + path, options)
const data = await response.json()
let res = {
code: response.status,
data: data
}
// Do something with res
}
Try this
function apiRequest(path, options) {
fetch("api/" + path, options)
.then(response => Promise.all([Promise.resolve(response.status), response.json()]))
.then(([status, data]) => {
let res = {
code: status //I want to put http status code here,
data: data
}
return res;
})
}
you can git it in the first then before you return response.json something like this
function apiRequest(path, options) {
fetch("api/")
.then((response) => {
let status = response.status;
console.log("status", status);
return response.json();
})
.then((data) => {
console.log(data);
});
}
apiRequest();
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 2 years ago.
I am trying to fetch data from an endpoint in react but don't know exactly how can I wait for the called function to return and then pass the cursor to next line.
FetchData.js
// ...
componentDidMount () {
const url = 'http...';
const options = {
headers: {'Content-Type': 'application/json'},
method: 'GET'
}
const data = fetchDataFromUrl(url, options);
this.setState({ item: data },
() => this.updateItem())
}
console.log(this.state.item)
// ...
fetchData.js
export const fetchDataFromUrl = (url, options) => {
const repsonse = fetch(url, options)
.then(res => res.json())
.then(data => {
return data
})
}
This does return the data but in console.log() I get undefined, after sometime (ie in some other function) I am able to see the data contents. How can I wait for the fetchDataFromUrl function to fetch the data before the state gets updated. I used
const data = async() => fetchDataFromUrl(url, options); but it does not help.
You can simply use, async and await for your requirement
componentDidMount () {
this.getData()
}
getData= async()=>{
const url = 'http...';
const options = {
headers: {'Content-Type': 'application/json'},
method: 'GET'
}
const data = await fetchDataFromUrl(url, options);
this.setState({ item: data },
() => this.updateItem())
}
user fetch function should also be async function.
export const fetchDataFromUrl = async(url, options) => {
const repsonse = await fetch(url, options);
return repsonse; //Modify you response
}
I need to merge data from API. I do a first call to an endpoint that gives me a list of ids, then I do a request for each id. My goal is to return a list with the responses of all requests but I lost myself in promises ...
My code runs on NodeJS. Here is the code :
const fetch = require('node-fetch')
const main = (req, res) => {
fetch('ENDPOINT_THAT_GIVES_LIST_OF_IDS')
.then(response => response.json())
.then(response => {
parseIds(response)
.then(data => {
console.log(data)
res.json(data)
// I want data contains the list of responses
})
})
.catch(error => console.error(error))
}
const getAdditionalInformations = async function(id) {
let response = await fetch('CUSTOM_URL&q='+id, {
method: 'GET',
});
response = await response.json();
return response
}
const parseIds = (async raw_ids=> {
let ids= []
raw_ids.forEach(function(raw_id) {
let informations = {
// Object with data from the first request
}
let additionalInformations = await
getAdditionalInformations(raw_id['id'])
let merged = {...informations, ...additionalInformations}
ids.push(merged)
})
return ids
})
main()
I get this error : "await is only valid in async function" for this line :
let additionalInformations = await getAdditionalInformations(raw_id['id'])
Help me with promise and async/await please.
You're almost there, just a slight bit of error here with your parentheses:
// notice the parentheses'
const parseIds = async (raw_ids) => {
let ids= []
raw_ids.forEach(function(raw_id) {
let informations = {
// Object with data from the first request
}
let additionalInformations = await getAdditionalInformations(raw_id['id'])
let merged = {...informations, ...additionalInformations}
ids.push(merged)
})
return ids
}
You are missing an async after forEach
const parseIds = (async raw_ids=> {
let ids= []
raw_ids.forEach(async function(raw_id) {
let informations = {
// Object with data from the first request
}
let additionalInformations = await
getAdditionalInformations(raw_id['id'])
let merged = {...informations, ...additionalInformations}
ids.push(merged)
})
return ids
})
One suggestion: you are mixing promises (.then()) with async/await. Prefer async/await is more readable.
Note that getAdditionalInformations inside forEach doesn't wait for it to be done before going to the next entry of the array.
You can use plain old for(var i=0; .... instead
I have the following promise function which uses fetch to get data from an API:
const getContacts = token =>
new Promise((resolve, reject) => {
fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
.then(response => response.json())
.then((data) => {
resolve(data);
})
.catch(err => reject(err));
});
This function is then called in a different file:
getContacts(token)
.then((data) => {
const contacts = data.data;
console.log(contacts);
})
.catch(err => console.error(err));
When there is a larger amount of data returned from the API, it is paginated. The response includes a link that needs to be fetched in order to get the next page. I want my code to first iterate through all pages and collect all data, then resolve the promise. When execution reaches the const contacts = data.data line, it should have data from every page (currently it returns only the first page).
What would be the best way to achieve this?
EDIT:
I tried recursion inside the getContacts function. This way I can iterate through all pages and get all data in one object, but I don't know what's the right way to resolve this back to the code, which initially called the function. The code below doesn't resolve correctly.
const getContacts = (token, allData, startFrom) =>
new Promise((resolve, reject) => {
if (startFrom) {
url = `${url}?${startFrom}`; // the api returns a set of results starting at startFrom (this is an id)
}
fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
.then(response => response.json())
.then((data) => {
let nextPageExists = false;
Object.assign(allData, data.data);
data.links.forEach((link) => {
if (link.rel === 'next') {
nextPageExists = true;
getContacts(token, allData, link.uri);
}
});
if (!nextPageExists) {
resolve({ data: allData });
}
})
.catch(err => reject(err));
});
First of all, do not use the new Promise constructor when fetch already returns a promise.
Then, just use a recursive approach and chain your promises with then:
function getContacts(token, allData, startFrom) {
return fetch(startFrom ? url + '?' + startFrom : url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}).then(response => response.json()).then(data => {
Object.assign(allData, data.data);
const nextPage = data.links.find(link => link.rel === 'next');
if (!nextPage)
return allData;
else
return getContacts(token, allData, nextPage.uri);
});
}
Here's a generic function using async/await syntax.
It returns itself until currentPage equals totalPages. You can retrieve these keys from your API response.
async function getData(perPage, page, options, allData = []) {
// fetch data
let base = 'https://api.example.com';
let url = `${base}?perPage=${perPage}&page=${page}`;
let response = await fetch(url, options);
let data = await response.json();
// push this data object (or data.data... whatever) into allData array
allData.push(data);
// get 'totalPages' and 'currentPage' (or whatever your API names these)
let totalPages = data.pagination.total_pages;
let currentPage = data.pagination.current_page;
if (currentPage == totalPages) {
// you're done
return allData;
} else {
// get the next page and repeat
page++;
return getData(perPage, page, options, allData);
}
}
Calling it:
const options = {
method: 'GET',
headers: {
Accept: 'application/json',
appId: 'APP_ID',
apiKey: 'APP_KEY',
'Content-Type': 'application/json'
}
};
let perPage = 100;
let page = 1;
getData(perPage, page, options).then((data) => {
console.log(data)
}).catch((error) => {
console.log(error)
})