I use fetch to get data for each element of an array. Those values are pushed into an array and I want to return the max of those values once all values are fetched.
For that I used a promise:
async function init() {
await maxWaste(nut0Codes).then(response => {
console.log(response);
});
}
function maxWaste(nutCodes) {
return new Promise(resolve => {
let waste = [];
nutCodes.forEach((element) => {
let query = queryMaxWaste(element);
fetch(address + encodeURIComponent(query))
.then(response => {
return response.json();
})
.then(response => {
waste.push(response.results.bindings[0].wasteGeneration.value);
console.log(waste);
});
});
let maxWaste = Math.max(waste);
resolve(maxWaste);
});
}
I am not sure where my mistake is but the resolve happens before the fetch is done :
I receive the zero from the console.log(response) and I don't know why it is not working.
Any advice would be appreciated!
If you're going to write code that uses async, you should actually leverage async. If this needs to be run synchronously-ish, you can await within a for loop. If you want them all to run simultaneously, use a map and Promise.all.
async function init() {
const response = await maxWaste(nut0Codes);
console.log(response);
}
async function maxWaste(nutCode) {
const waste = [];
for (const element in nutCode) {
const query = queryMaxWaste(element);
const response = await fetch(address + encodeURIComponent(query));
const json = await response.json();
waste.push(json.results.bindings[0].wasteGeneration.value);
console.log(waste);
}
const maxWaste = Math.max(waste);
return maxWaste;
}
You could also try writing it like this so that you don't wait for each response to complete before running the next fetch:
async function init() {
const response = await maxWaste(nut0Codes);
console.log(response);
}
async function maxWaste(nutCode) {
const waste = await Promise.all(nutCode.map(async element => {
const query = queryMaxWaste(element);
const response = await fetch(address + encodeURIComponent(query));
const json = await response.json();
return json.results.bindings[0].wasteGeneration.value;
}));
return Math.max(waste);
}
Related
I am trying to get data from a function call and I am keeping it in a while loop so the function will be called again and again if data is not received. What I meant with the data is the data1,data2 and data3 arrays that I have returned from the getData function should be populated and not have a null value. But I am not able to find the solution.
Here is my code :
router.get('/someurl' , async(req,res) => {
let data = [];
while(data.length == 0)
{
data = await functionCall()
console.log(data)
}
})
And here is the format of my functionCall code :
const unirest = require("unirest");
const cheerio = require('cheerio')
const getData = async() => {
const data1 = [] , data2 = [] , data3 = [] ;
try {
const response = await unirest
.get('url')
.headers({'Accept': 'application/json', 'Content-Type': 'application/json'})
.proxy("proxy")
const $ = cheerio.load(response.body)
$('hided').each((i,el) =>
{
data1[i] = $(el)
.find('hided')
.text()
data2[i] = $(el)
.find('hided')
})
$('hided').each((i,el) =>
{
data3[i] = $(el)
.find('hided')
.text()
})
if(data1.length && data2.length && data3.length))
{
return [data1 , data2 , data3]
}
}
catch(error)
{
console.log("Error" , error);
}
return [];
}
module.exports = getData
Firstly remove the if statement, as it is literally what the while loop is doing. then remove await, as this is not in an async function, and it will make an error.
Try something like this.
const fetchData = async ()=>{
data = await functionCall()
if(data.length==0){
fetchData()
}else{
return}
}
Assuming that fetchData is an API call there's no reason to initially define data. You can just log the results of calling that API until the results are an empty array.
const data = [1, 2, 3, 4];
// A simple API that returns a reduced array
// on every call
function mockApi() {
return new Promise(res => {
setTimeout(() => {
res([...data]);
data.shift();
}, 1000);
});
}
// Call the API, and if the the response
// has zero length log "No data" otherwise
// log the data, and call the function again.
async function fetchData() {
const data = await mockApi();
if (!data.length) {
console.log('No data');
} else {
console.log(data);
fetchData();
}
}
fetchData();
So I want to pull data from two different endponts, and send them both to a function at the same time, as the parameters for said function.
function otherFunc(jsonData1, jsondata2){
//do stuff
}
function getJsonData(url1,url2){
fetch(url1);
fetch(url2);
.then((response) => response.json())
.then((data) => otherFunc(data));
}
getJsonData(someUrl1, someOtherUrl2);
So I know how to send one set of data to a function, and I know how to make multiple get requests but I don't know how I can send both sets of jsonData to the same function as params.
Thanks in advance
A little clearer using async/await
function otherFunc(jsonData1, jsonData2) {
//do stuff
}
async function getJsonData(url1, url2) {
const res1 = await fetch(url1);
const res2 = await fetch(url2);
const json1 = await res1.json();
const json2 = await res2.json();
otherFunc(json1, json2);
}
getJsonData(someUrl1, someOtherUrl2);
Alternatively using Promise.all()
function otherFunc(jsonData1, jsonData2) {
//do stuff
}
async function getJsonData(url1, url2) {
const resArr = await Promise.all(
[url1, url2].map(url => fetch(url))
);
const [json1, json2] = await Promise.all(
resArr.map(res => res.json())
);
otherFunc(json1, json2)
}
getJsonData(someUrl1, someOtherUrl2);
or...
function otherFunc(jsonData1, jsonData2) {
//do stuff
}
function getJsonData(url1, url2) {
Promise.all([
fetch(url1),
fetch(url2)
]).then(responses => //map the returned promises to resolve json
Promise.all(responses.map(res => res.json()))
).then(([json1, json2]) => // destructure resolved json
otherFunc(json1, json2)
).catch(error =>
// if there's an error, log it
console.log(error));
}
getJsonData(someUrl1, someOtherUrl2);
function otherFunc(jsonData1, jsondata2){
//do stuff
}
function getJsonData(url1,url2){
fetch(url1).then(res1 => {
fetch(url2).then(res2 => {
// here you have both responses ready
otherFunc(res1.json(), res2.json());
})
})
}
getJsonData(someUrl1, someOtherUrl2);
Solution 2 using async-await:
async function getData(url = '') {
const response = await fetch(url);
return response.json();
}
function otherFunc(jsonData1, jsondata2){
//do stuff
}
async function getJsonData(url1,url2){
const res1 = await getData(url1);
const res2 = await getData(url2);
otherFunc(res1, res2);
}
getJsonData(someUrl1, someOtherUrl2);
I would add the two URLs into an array in getJsonData and loop through them storing the results into an array
function getJSONData(url1,url2){
const urlArr = [url1, url2]
const resultsArr = []
for(let x = 0; x < arr.length){
fetch(arr[x])
}
return resultsArr
}
Inspired by this Wait for multiple promises to finish maybe this can work and avoir having any cascade wait.
function otherFunc(jsonData1, jsondata2){
//do stuff
}
function getJsonData(url1,url2){
var json1, json2;
const promise1 = fetch(url1).then(res1 => { json1 = res1.json() });
const promise2 = = fetch(url2).then(res1 => { json2 = res1.json() });
Promise.all([promise1, promise2]).then((values) => {
otherFunc(values[0], values[1]);
//Or if order of parmeter matter
otherFunc(json1, json2);
});
}
getJsonData(someUrl1, someOtherUrl2);
The Problem is with the uplines.push.
I always get an empty uplines array so the last part of the code doesn't run. The promises resolve later and I get the correct data. May I know how to go about doing it the correct way?
const getAllUplines = async () => {
uplines = [];
const findUser = async (userFid) => {
const userDoc = await firestore.collection("users").doc(userFid).get();
if (userDoc.exists) {
const user = { ...userDoc.data(), id: userDoc.id };
console.log(user);
uplines.push(user);
if (user.immediateUplineFid) {
findUser(user.immediateUplineFid); //self looping
}
} else {
console.log("No User Found");
return null;
}
};
sale.rens.forEach(async (ren) => {
findUser(ren.userFid);
});
console.log(uplines);
return uplines;
};
let uplines = await getAllUplines();
console.log(uplines);
uplines = uplines.filter(
(v, i) => uplines.findIndex((index) => index === v) === i
); //remove duplicates
uplines.forEach((user) => {
if (user.chatId) {
sendTelegramMessage(user.chatId, saleToDisplay, currentUser.displayName);
console.log("Telegram Message Sent to " + user.displayName);
} else {
console.log(user.displayName + " has no chatId");
}
});
There are a few things that you have missed out while implementing the async call, which are explained in the inline comments in the code snippet.
A short explanation for what happened in your code is that in the line sale.rens.forEach you are passing an async function in the argument, which does not make any difference to the function forEach, it will execute it without waiting for it to complete.
Therefore in my answer I am using Promise.all to wait for all the async function calls to complete before returning the result.
// This is wrapped in an immediately executed async function because await in root is not supported here
(async () => {
const mockGetData = () => new Promise(resolve => setTimeout(resolve, 1000));
const sale = {
rens: [
{ userFid: 1 },
{ userFid: 2 },
{ userFid: 3 }
]
};
const getAllUplines = async () => {
const uplines = [];
const findUser = async (userFid) => {
// Simulating an async function call
const userDoc = await mockGetData();
console.log("User data received");
uplines.push(`User ${userFid}`);
};
const promises = [];
sale.rens.forEach(ren => { // This function in foreach does not have to be declared as async
// The function findUser is an async function, which returns a promise, so we have to keep track of all the promises returned to be used later
promises.push(findUser(ren.userFid));
});
await Promise.all(promises);
return uplines;
};
let uplines = await getAllUplines();
console.log(uplines);
})();
In order to get the results of getAllUplines() properly, you need to add await to all async functions called in getAllUplines().
const getAllUplines = async () => {
uplines = [];
const findUser = async (userFid) => {
const userDoc = await firestore.collection("users").doc(userFid).get();
if (userDoc.exists) {
const user = { ...userDoc.data(), id: userDoc.id };
console.log(user);
uplines.push(user);
if (user.immediateUplineFid) {
await findUser(user.immediateUplineFid); //self looping
}
} else {
console.log("No User Found");
return null;
}
};
sale.rens.forEach(async (ren) => {
await findUser(ren.userFid);
});
console.log(uplines);
return uplines;
};
I am trying to build a React project where I have a component that fetches data from a remote server and plots it.
async function fetchRemoteData(name) {
let dataItem = null;
if (name) {
await fetch("/endpoint/" + name)
.then((res) => res.json())
.then((payload) => {
console.log("payload = " + JSON.stringify(payload));
dataItem = payload;
});
} else {
console.log("Error: No data for " + name);
}
return dataItem;
}
var RenderPlot = ({ match }) => {
let remoteObject = fetchRemoteData(match.params.data);
console.log("remoteObject = " + JSON.stringify(remoteObject));
// .... rendering code
// .... and other irrelevant stuffs to this question
}
If I run this code, on the console, I'm seeing the remoteObject is empty, but the payload inside the function fetchRemoteData() is not.
remoteObject = {}
payload = {"data":"some value"}
Now, let's say I have data stored in the browser (which is obviously I'm not supposed to do, but anyway) and if I fetch it using a generic function call, I'm not having problem.
function fetchLocalData(name) {
// read the data from a local js object
// and return it
}
var RenderPlot = ({ match }) => {
let remoteObject = fetchRemoteData(match.params.data);
console.log("remoteObject = " + JSON.stringify(remoteObject));
let localObject = fetchLocalData(match.params.data);
console.log("localObject = " + JSON.stringify(lovalObject.m))
// .... rendering code
// .... and other irrelevant stuffs to this question
}
and the output --
localObject = 4
remoteObject = {}
payload = {"data":"some value"}
So, what's happening is the code gets the localData from fetchLocalData(), then calls fetchRemoteData() but it doesn't wait if the data is received or not. And then, keeps doing whatever in the rest of the code.
What I want is, the code should wait until the remoteData is received and then proceed to the next steps.
How do I do that?
I can recommend you to store your received data in state of component like example below. If I understood you correctly -
const RenderPlot = ({ match }) => {
const [data, setData] = useState()
const fetchData = useCallback(async name => {
try {
const response = await fetch("/endpoint/" + name);
setData(response.data); // or whatever, depends on how you set your API up.
} catch (e) {
console.log(e)
}
}, [])
// in this case it works as componentDidMount;
useEffect(() => {
;(async function() {
await fetchData(match.params.data);
})()
}, [match.params])
return (<div>...your jsx plot implementation</div>)
}
According to this flow you can compose two fetching,
const fetch1 = useCallback(async () => {your first call api}, [])
const fetch2 = useCallback(async () => {your second call api}, [])
useEffect(() => {
;(async function() {
await fetch1...
await fetch2
})()
}, [match.params])
Or it can be like this
const fetchData = useCallback(name => {
try {
const response1 = await fetch("/endpoint1/" + name);
const response2 = await fetch("/endpoint2/" + name);
// this line would be read after both of your responses come up. Here you can make some operation with both data.
setData(response.data); // or whatever, depends on how you set your API up.
} catch (e) {
console.log(e)
}
}, [])
The reason is that ...return dataItem executes before that promise resolve the request. You need to use async await:
async function fetchRemoteData(name) {
let dataItem = null;
if (name) {
const res = await fetch("/endpoint/" + name);
dataItem = await res.json();
} else {
console.log("Error: No data for " + name);
}
return dataItem;
}
I am trying to load data from firebase by calling a function in which it filters data and returns them.
When I call this function in my main function, it returns "undefined". I know the data is there (console.log(postsArray)) prints the data but I guess the return executes before data is loaded.
What am I doing wrong?
calling_Function_in_Main = async () => {
const data = await FirebaseData ();
console.log(data);
};
FirebaseData is the function that I call in my main function to load data and to return them
let postsArrays=[];
const FirebaseData = async () => {
const getViewableLink = async (link) => { //some function };
const loadData = async () => {
const database = firebase.database();
const data = database.ref();
const loadProfile = data
.child('Posts')
.orderByChild('Active')
.equalTo(true)
.once('value', function gotData(data) {
Object.values(readInfo).forEach(async (element) => {
element.Option1Link = await getViewableLink(
preLink + element.Option1Link,
);
postsArray.push(element);
}
});
})
.catch((error) => {
console.log(error);
}
})
.then((postsArray) => {
console.log(postsArray);
return postsArray;
});
};
await loadData();
};
export default FirebaseSwipeData;
You can't use foreach with async/await because It is not asynchronous. It is blocking.
you have 2 ways to fix this:
1- Reading in sequence: you can use for...of loop
for(const element of Object.values(readInfo)) {
element.Option1Link = await getViewableLink(
preLink + element.Option1Link,
);
postsArray.push(element);
}
2- Reading in parallel: you can use Promise.all
await Promise.all(Object.values(readInfo).map(async (element) => {
element.Option1Link = await getViewableLink(
preLink + element.Option1Link,
);
postsArray.push(element);
}));
Hope that solves the problem, for you