Problem with .push with Asynchronous function - javascript

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;
};

Related

Array defined outside of function not being populated after awaiting an async call

I have the following code where in the getStuff function I'm making an external async call and storing the result in result. From there I'm looping over result.Content.Ids and pushing the Ids in myArray.
When getStuff() gets called in run(), further down the code I need to access myStuff array but if I console.log it out, it shows up as empty.
const myArray = [];
const getStuff = async (val) => {
const other = {}
if(val) other.newestVal = val;
const result = await someCall(other);
result.Content.Ids.forEach((id) => myArray.push(id));
if (result.AFieldExists) {
getStuff(result.newVal)
}
}
const run = async () => {
await getStuff();
// other code below that uses myArray
// but its empty when I log it
}
run();
I'd have expected myArray to be populated since I'm awaiting getStuff() in run(). What am I doing wrong here?
Your recursive call:
if (result.AFieldExists) {
getStuff(result.newVal)
}
is incorrect, since you're not waiting for the result - only the first getStuff call will be waited for. You need:
const getStuff = async (val) => {
const other = {}
if(val) other.newestVal = val;
const result = await someCall(other);
result.Content.Ids.forEach((id) => myArray.push(id));
if (result.AFieldExists) {
return getStuff(result.newVal)
}
}
You can also clean it up a bit to avoid the ugly outer variable:
const getStuff = async (val, output = []) => {
const other = {}
if (val) other.newestVal = val;
const result = await someCall(other);
output.push(...result.Content.Ids);
return result.AFieldExists
? getStuff(result.newVal, output)
: output;
}
const run = async () => {
const output = await getStuff(); // pass initial value here?
}
you need to put your await calls in try{} catch(){},
and also your recursive call to getStuff() was with out the key word await :
async function someCall() {
return {
Content: {Ids: [1, 2, 3]}
}
}
const myArray = [];
const getStuff = async (val) => {
const other = {}
let result;
if(val) other.newestVal = val;
try{
result = await someCall(other);
}catch(err){
console.log('error from some call' ,err);
}
console.log(myArray)
result.Content.Ids.forEach((id) => myArray.push(id));
console.log(myArray)
if (result.AFieldExists) {
try{
await getStuff(result.newVal)
}catch(err){
console.log('error from get stuff in get stuff' , err);
}
}
}
const run = async () => {
console.log("run")
try{
await getStuff();
}catch(err){
console.log('error' , err);
}
console.log("end run")
console.log(myArray)
// other code below that uses myArray
// but its empty when I log it
}
run();

React JS - promise returns before execution completes. Async function does not work

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

How to get the result of async / await function?

I would like to return an object from the the async / await function A to pass it to another function.
Currently what I get as a result is Promise{ <pending> }' or undefined.
function A:
const parseRss = data => data.forEach(rssLink => {
const getFeed = async () => {
try {
const feed = await rssParser.parseURL(rssLink.rss);
const emailContent = {
emailBody: {
title: feed.title,
content: []
}
}
feed.items.forEach(item => {
feedObj.emailBody.content.push(`${item.title} : ${item.link}`)
});
return emailContent;
} catch (e) {
console.error(e);
}
};
return (async () => {
return await getFeed();
})();
});
Function B:
try {
const data = await getDataWithRss();
const emailData = await parseRss([{rss:'http://reddit.com/.rss'}]); // emailData is undefined
return formatEmail(emailData);
} catch (error) {
console.log(error);
}
How do I return emailContent from function A to use it in function B?
Thanks!
Since you made getFeed as async, no need another async. You are looping through, so return an array of promises. Once the you call use Promise.all to resolve. Since there could be multiple urls to fetch.
const parseRss = (data) =>
data.map((rssLink) => {
const getFeed = async () => {
try {
const feed = await rssParser.parseURL(rssLink.rss);
const emailContent = {
emailBody: {
title: feed.title,
content: [],
},
};
feed.items.forEach((item) => {
feedObj.emailBody.content.push(`${item.title} : ${item.link}`);
});
return emailContent;
} catch (e) {
console.error(e);
}
};
return getFeed();
});
try {
const data = await getDataWithRss();
const emailData = await Promise.all(parseRss([{rss:'http://reddit.com/.rss'}])); // emailData is undefined
return formatEmail(emailData);
} catch (error) {
console.log(error);
}
await will not work inside a forEach loop. Use a for...in loop instead.
actually, getFeed() is not necessary inside inner scope, you can use async in map callback:
const parseRss = data => data.map(async rssLink => {
const feed = await rssParser.parseURL(rssLink.rss);
const emailContent = {
emailBody: {
title: feed.title,
content: []
}
};
feed.items.forEach(item => {
feedObj.emailBody.content.push(`${item.title} : ${item.link}`)
});
return emailContent;
});

What am I doing wrong with this async function?

I am trying to learn/implement async functions, and I'm unsure why I am not able to get it to work. The 'users' in my async function are coming back undefined. However, the function that makes the database call works just fine.
const getUsers = () => {
const database = firebase.database()
const users = database.ref('users');
users.on('value', function(snapshot) {
const users = []
snapshot.forEach(function(childSnapshot) {
let user = childSnapshot.val();
users.push(user)
});
return users
});
}
async function generateRandomPotentialPartner() {
try {
const users = await getUsers();
console.log(users)
} catch (error) {
console.log(error)
}
}
Use the promise version of on() instead of using the callback version and return that promise from getUsers() function.
Using the callback approach you are currently using, nothing is being returned at all since a return in a callback does not return to the outer function
const getUsers = () => {
const database = firebase.database()
const users = database.ref('users');
// return the on() promise instead of using callback
return users.on('value').then(snapshot => {
const users = []
snapshot.forEach(childSnapshot => {
users.push(childSnapshot.val())
});
return users
});
}
const getUsers = () => {
const database = firebase.database()
const users = database.ref('users');
users.on('value', function(snapshot) {
const users = []
snapshot.forEach(function(childSnapshot) {
let user = childSnapshot.val();
users.push(user)
});
return new Promise((resolve,reject) => {
if(users.length == 0){
reject("There are no users"); //--- Handle rejection
}else {
resolve(users);
}
});
});
}
Your problem is that you are not returning Promises. Asynchronous Javascript is only about promises and chaining.

Await inside loop before move to another iteration

I am trying to send message through an API using a function. When function do his duty it returns back a value which is messageLodId and it needs to be updated at Attendence in main loop. But when I execute this code, value comes undefined.
There are two questions:
1) Is the structure right?
2) If yes, please provide the answer for this problem.
//Posting SMS
router.post('/sms/', async function(req, res) {
let attendenceRecordId = parseInt(req.body.attendenceRecordId);
let result = await AttendenceRecord.findOne({where: {id: attendenceRecordId }, include: [ {model: Attendence, include: [{model: Student}
]}, {
model: Class
}], order: [['date', 'DESC']]});
if(!result) {
res.sendStatus(404);
}
for await (let attendence of result.attendences){
let messageLogId = await sendSMS(attendence);
console.log("Message ID: ", messageLogId);
Attendence.update(
{ smsLogId: messageLogId },
{ where: { id: attendence.id } }
);
}
AttendenceRecord.update(
{ isMessageSent:true },
{ where: { id: result.id } }
);
res.send({messageSent: true});
});
Here is the function definition. Right now I am just returning 1.
In real world the URL returns a code.
async function sendSMS(attendence){
//console.log(target);
setTimeout(function(){
let message = `Respected Parent, your son/daughter ${attendence.student.name} is absent on ${attendence.date}`;
let messageURL = encodeURI(message);
let api = 'SOME VALUE';
let phone = attendence.student.fphone.substring(1, 11);
let target = `http://BASE_URL/api.php?key=${api}&receiver=92${phone}&sender=DigitalPGS&msgdata=${messageURL}`;
return 1;
}, 2000);
}
You should return promise from sendSMS. Resolve promise in setTimeout callback function.
function sendSMS(attendence){
//console.log(target);
return new Promise((resolve, reject) => {
setTimeout(function(){
let message = `Respected Parent, your son/daughter ${attendence.student.name} is absent on ${attendence.date}`;
let messageURL = encodeURI(message);
let api = 'SOME VALUE';
let phone = attendence.student.fphone.substring(1, 11);
let target = `http://BASE_URL/api.php?key=${api}&receiver=92${phone}&sender=DigitalPGS&msgdata=${messageURL}`;
resolve(1);
}, 2000);
});
}
You should have sendSMS return a promise, and await that:
exec();
async function exec()
{
var records = [1,2,3];
for(var i=0;i<records.length;i++)
{
var messageLogId = await sendSMS(records[i]);
console.log("Result received from resolve", messageLogId);
}
}
function sendSMS(record)
{
// simulate an async method:
return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log("Send sms for record", record);
resolve(1);
}, 1000);
});
}
Note that the setTimeout here is just to demonstrate an asynchronous action. In the real world your sendSMS function will no doubt call an API, which will itself be asynchronous - just return the promise from that (or, wrap the call in a promise if the API client doesn't return one).
First, make your function Promisify. Then chunk your function and call that function in the for loop and handle with Promise.all().
const manyPromises = [];
for (const attendence of result.attendences) {
manyPromises.push(sendSmsAndUpdateStatus(attendence));
}
// Execution wait until all promises fulfilled/rejected
const result = await Promise.all(manyPromises);
const sendSmsAndUpdateStatus = async (attendence) => {
try {
const messageLogId = await sendSMS(attendence);
const updateObj = { smsLogId: messageLogId };
const condition = { where: { id: attendence.id } };
const result = await Attendence.update(updateObj, condition);
return { result, messageLogId };
} catch (err) {
logger.error(err);
throw err;
}
};
const sendSMS = (attendence) => {
return new Promise((resolve) => {
setTimeout(() => {
const message = `Respected Parent, your son/daughter ${attendence.student.name} is absent on ${attendence.date}`;
const messageURL = encodeURI(message);
const api = 'SOME VALUE';
const phone = attendence.student.fphone.substring(1, 11);
const target = `http://BASE_URL/api.php?key=${api}&receiver=92${phone}&sender=DigitalPGS&msgdata=${messageURL}`;
return resolve(1);
}, 2000);
});
};
Summary make sure your function sendSMS will return Promise, afterwards you can handle it with async/await or .then().catch() approaches.

Categories

Resources