How do i use array.push on async loop? - javascript

I am trying to push within a loop in an asynchronous function, but the data that is entered into this array is not saved after the loop ends. What would I be doing wrong?
for (cont = 0; cont < 3; cont += 1) {
console.log(cont);
sqs.receiveMessage(paramsReceiveMessage, (err, data) => {
if (err) {
console.log('Receive Error', err);
} else if (data.Messages) {
const [{ MD5OfBody }] = data.Messages;
sqsMessages.push(MD5OfBody);
console.log(sqsMessages);
}
});
}
const result = await Promise.all(sqsMessages);
console.log(result);
return result;
My response:
2019-11-04T14:35:12.219Z f00e1408-3ec6-4290-914a-eae4efb23939 INFO 0
2019-11-04T14:35:12.221Z f00e1408-3ec6-4290-914a-eae4efb23939 INFO 1
2019-11-04T14:35:12.223Z f00e1408-3ec6-4290-914a-eae4efb23939 INFO 2
2019-11-04T14:35:12.224Z f00e1408-3ec6-4290-914a-eae4efb23939 INFO []

Your callback function in sqs.receiveMessage() is still active when the code later reaches the await Promise.all(), so your sqlMessages-array is still empty. You need to wait for the promises in that array to be completed.
In other words, create an array with promises and then wait for them. Something like this:
const promises = [];
for (cont = 0; cont < 3; cont += 1) {
console.log(cont);
promises.push(new Promise((resolve, reject) => {
sqs.receiveMessage(paramsReceiveMessage, (err, data) => {
if (err) {
console.log('Receive Error', err);
reject(err);
} else if (data.Messages) {
const [{ MD5OfBody }] = data.Messages;
sqsMessages.push(MD5OfBody);
console.log(sqsMessages);
resolve(MD5OfBody);
}
});
}));
}
const result = await Promise.all(promises);
console.log(result);

Looking at your code I would expect the second to last statement ( console.log(result); ) to execute before anything else - and that means the consoled result will be empty.
If the rest of the code is correct (which I can't quite tell), you should be able to console.log the result (last line of code shown) in the place where the return comes back.
pseudocode:
containing function(){
console.log(yourCodeInFunction());
}
That console should give a result, because it won't run till your messages are received.

Related

How to execute code when foreach ends in javascript

When the code below runs I expect score to have a value (lets say: {"A": 1, "B": 2}), but when I print it I get an empty dict ({}).
I have tried to use promises but the result is the same.
driversBySeason(season) {
var query = `SELECT raceId FROM races WHERE year = ${season}`;
var score = {};
this.con.query(query, (err, drop) => {
if (err) {
console.error(err);
}
drop.forEach((element) => {
var raceId = element["raceId"];
query = `SELECT driverId, points FROM results WHERE raceId = ${raceId}`;
this.con.query(query, (err, drop) => {
if (err) {
console.error(err);
}
drop.forEach((element) => {
if (score[element["driverId"]] == undefined) {
score[element["driverId"]] = 0;
} else if (score[element["points"]] != undefined) {
score[element["driverId"]] += element["points"];
}
});
});
});
console.log(score);
});
}
First of all you need to change your .forEach loop to for const of loop or just a regular for loop because it just fires the code inside the callback and never waits for it. And then you need to change your this.con.query(...) function which has callback to promises too. Your code should be like this:
const asyncQuery = (query) => new Promise((resolve, reject) => {
this.con.query(query, (err, drop) => {
if (!!err) {
console.log(error);
return reject(err)
}
return resolve(drop);
})
});
async function driversBySeason(season) {
var query = `SELECT raceId FROM races WHERE year = ${season}`;
var score = {};
const drop = await asyncQuery(query).catch(err => err /* some error handling */);
for (const element of drop) {
var raceId = element["raceId"];
query = `SELECT driverId, points FROM results WHERE raceId = ${raceId}`;
const drop2 = await asyncQuery(query).catch(err => err /* some error handling */);
drop2.forEach((element) => {
if (score[element["driverId"]] == undefined) {
score[element["driverId"]] = 0;
} else if (score[element["points"]] != undefined) {
score[element["driverId"]] += element["points"];
}
});
}
console.log(score);
}
You are outside of your callback. When this executes the code continues to run past "this.con.query". You are seeing your empty object that was assigned at the top do to this. Go inside the callback after the drop.forEach where you assign the values, or convert to an async/await approach.
when you call driversBySeason your score variable has value of {}
and that is value for console.log() when you call it because of how closers are works and you updating score with callback function that happen later in time...
you get better understanding if you use promise...not callback

What are different ways to handle asynchronous calls (apis) inside a for loop or array method?

I have been trying to create an api like this where I tried different things like using array methods like map/filter/reduce where either I get pending promises or result returned before execution of api call.
So my doubt is -->
How do I get total number of drawn matches of all pages ( so I need to add data.total from all pages).
How to better understand this behaviour.
async function getNumDraws(year) {
const goals = [...Array(11).keys()];
let result = 0;
console.log(`before loop ${new Date()}`);
for(let goal of goals){
console.log(`in loop before await ${new Date()}`);
await require('https').get(`https://jsonmock.hackerrank.com/api/football_matches?year=${year}&team1goals=${goal}&team2goals=${goal}`,res=>{
let data="";
res.on('data', (chunk) => {
data += chunk;
});
// The whole res has been received. Print out the result.
res.on('end', () => {
data=JSON.parse(data);
console.log(result,data.total)
result= result + data.total;
});
})
console.log(`in loop after await ${new Date()}`);
}
console.log(`after loop ${new Date()}`);
return result;
}
console.log(getNumDraws(2011));
https.get is a callbacked function so await won't work. You should promisify it first like they did in this other SO question;
const https = require("https"); // only require this once
const getJSONAsync = url => new Promise((resolve, reject) => { // define a function getJSONAsync which returns a Promise that wraps the https.get call, getJSONAsync is awaitable
let req = https.get(url, res => { // make the https.get request
if(res.statusCode < 200 || res.statusCode >= 300) { // if the status code is an error one
return reject(new Error('statusCode=' + res.statusCode)); // reject and skip the rest
}
let body = []; // otherwise accumulate..
res.on('data', chunk => body.push(chunk)); // ..the data
res.on('end', () => { // on end
try {
body = JSON.parse(Buffer.concat(body).toString()); // try to JSON.parse the data
} catch(e) {
reject(e); // reject if an error occurs
}
resolve(body); // resolve the parsed json object otherwise
});
});
req.on("error", error => reject(error)); // reject if the request fails too (if something went wrong before the request is sent for example)
});
async function getNumDraws(year) {
let result = 0;
for(let goal = 0; goal < 11; goal++) {
let data = await getJSONAsync(`https://jsonmock.hackerrank.com/api/football_matches?year=${year}&team1goals=${goal}&team2goals=${goal}`);
result += data.total;
}
return result;
}
Note: getJSONAsync is not specific to getNumDraws, you can use it somewhere else if you need it, and since it returns a Promise you can either await it like getNumDraws does or use it with then/catch blocks like so:
getJSONAsync("url")
.then(data => {
// data is the parsed json returned by the request
})
.catch(error => {
// the error message if something fails
})

How to use promises so that loop doesn't hang

I am trying to loop through records in a database, in order to compile an array (cardsToInsert) that I will write to another database.
I was getting stuck because the array was writing to the database before the loop finished, I know I need to use promises / async functions to achieve what I want, but I'm pretty sure I'm doing something wrong with my promises.
The code works for a few loops (it goes for about 6-10 loops, it's supposed to loop 16 times), but then hangs while trying during wixData.get (or it hangs on a different promise that is part of buildCard).
// wixData.get is a function that returns a promise
async function loopCards(cardsToGet) {
let writeCard
let buildCard
for (let index = 0; index < cardsToGet.length; index++) {
const cardToGet = cardsToGet[index].card
buildCard = await wixData.get("Card", cardToGet)
.then((card) => {
return card
})
.catch((err) => {
let errorMsg = err;
return errorMsg
});
writeCard = await buildingCard(buildCard)
cardsToInsert.push(writeCard)
}
return cardsToInsert
}
What am I doing wrong? (or what is the key thing I'm doing wrong that is stopping this working, I'm sure there is plenty to be improved here!)
UPDATE
I've now updated the code and it loops through fine.
async function loopCards(cardsToGet) {
console.log('Start')
let writeCard
let buildCard
for (let index = 0; index < cardsToGet.length; index++) {
const cardToGet = cardsToGet[index].card
buildCard = wixData.get("Card", cardToGet)
.then(async (card) => {
writeCard = await buildingCard(card)
cardsToInsert.push(writeCard)
})
.catch((err) => {
let errorMsg = err;
return errorMsg
});
}
return cardsToInsert
}
How do I get it to wait for the loop to finish before finally returning cardsToInsert?
Your mix of async/await and .then is not really best practice
This should work, and will return once cardsToInsert is populated
async function loopCards(cardsToGet) {
const cardsToInsert = [];
for (let cardToGet of cardsToGet) {
try {
const card = await wixData.get("Card", cardToGet);
const writeCard = await buildingCard(card);
cardsToInsert.push(writeCard);
}
catch(err) {
let errorMsg = err;
return errorMsg;
}
}
return cardsToInsert;
}
better still, you really don't need to handle any errors here, since the calling function could do that
So it becomes even simpler
async function loopCards(cardsToGet) {
const cardsToInsert = [];
for (let cardToGet of cardsToGet) {
const card = await wixData.get("Card", cardToGet);
const writeCard = await buildingCard(card);
cardsToInsert.push(writeCard);
}
return cardsToInsert;
}
then using it could be like
loopCards(cards)
.then(result => doSomethingWihtResult)
.catch(error => handleError);
or if calling from an async function
try {
let result = await loopCards(cards);
// do something with result
} catch(error) {
// handle Error
}

How do I log responses in the correct order using Async code

I need to create a function that runs a 'getFile' function on each item in an array. The getFile function logs 'File contents of x' x being whatever element is in the array.
Currently, I have a working function that runs the getFile on the array and waits for the final response before logging the results.
However, I now need to log the responses as I receive them in order. For example, if my array is [1, 2, 3, 4, 5] currently it logs 'File contents of x' in a random order, so if it was to return the logs, 3 then 4 then 1. As soon as I receive 1, I need to log that, then once I receive 2 logs that and so on.
I will insert my current code below. The problem I'm having is I need to know when the 'empty space' in my array becomes populated so I can log it in real time. Therefore allowing my user to see the result build up rather than just having to wait until all the responses have come back
function fetchContentOfFiles(fileNames, testCB) {
const fileContent = [];
let counter = 0;
fileNames.forEach((file, i) => {
getFile(file, (err, fileName) => {
if (err) console.log(err)
else {
fileContent[i] = fileName;
counter++
if (counter === fileNames.length) {
testCB(null, fileContent)
};
console.log(fileContent)
};
});
});
};
The cleanest way to write this would be to use a for loop inside an async function. Promisify getFile so that it returns a Promise, then await it in every iteration of the loop. At the end of the loop, call the callback:
const getFileProm = file => new Promise((resolve, reject) => {
getFile(file, (err, fileName) => {
if (err) reject(err);
else resolve(fileName);
});
});
async function fetchContentOfFiles(fileNames, testCB) {
const fileContent = [];
try {
for (let i = 0; i < fileNames.length; i++) {
fileContent.push(
await getFileProm(fileNames[i])
);
}
} catch(e) {
// handle errors, if you want, maybe call testCB with an error and return?
}
testCB(null, fileContent);
}
It would probably be even better if fetchContentOfFiles was called and handled as a Promise rather than with callbacks, and then the errors can be handled in the consumer:
async function fetchContentOfFiles(fileNames) {
const fileContent = [];
for (let i = 0; i < fileNames.length; i++) {
fileContent.push(
await getFileProm(fileNames[i])
);
}
return fileContent;
}
fetchContentOfFiles(arr)
.then((fileContent) => {
// do stuff with fileContent
})
.catch((err) => {
// something went wrong
});

Infinite loop in Nodejs

While making a link Shortner script using nodejs, i encountered the following problem:
my program went on an infinite loop for a reason i ignore
here is the code:
function makeShort() {
var short = "";
var cond = true;
while(cond){
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for(var i = 0; i < length; i++){
short += possible.charAt(Math.floor(Math.random() * possible.length));
}
let query = {short:short};
Link.findOne(query, (err, link)=>{
if(err) throw err;
if(!link){
console.log("here");
cond = false;
}
});
}
return short;
}
to then use it here :
router.post('/', (req, res)=>{
let short = makeShort();
const newLink = new Link({
url: req.body.url,
short:short
});
newLink.save().then(link => {
res.json(link);
});
});
the idea is that i generate a random string (5 characters), and then, if it exists i create another one and so on.. until i find one that isn't used ( the database is empty btw so there is no reason for it to go infinite loop ).
You can loop over and test for values in your database using async/await. What we do is convert your function to an async function, then create a new function that will return a promise which will resolve true/false.
Next we call that function in the while loop and await for a result which will contain true/false we then set that to the variable cond and continue the loop.
It would look something like this:
async function makeShort(length) {
let cond = true;
while (cond) {
let short = (Math.random() * 1000).toString(32).replace(/\./g, '').substr(0, length);
let query = { short: short };
cond = await findOne(query);
}
return short;
}
function findOne(query) {
return new Promise(resolve => {
Link.findOne(query, (err, link) => {
if (err) resolve(false);
if (!link) {
return resolve(false);
}
return resolve(true);
});
})
}
We then can call it using let short = await makeShort() like this (we also have to the make (req, res) function async):
router.post('/', async (req, res) => {
let short = await makeShort();
const newLink = new Link({
url: req.body.url,
short: short
});
newLink.save().then(link => {
res.json(link);
});
});
Don't mix synchronous looping and asynchronous condition updating. Something like this is guaranteed to run the while body as many times as it can before that DoSomething call returns a result:
while(cond) {
// call something async. don't wait for a result.
DoSomething.asynchronous( () => { cond = false; });
// then immediately restart the iteration
}
So don't do that. Make your makeShort generate a shortform string asynchronously.
const symbols = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const symbolCount = symbols.length;
function makeShort(howMany) {
howMany = howMany || 5;
let short = "";
while(howMany--) {
short += symbols[(Math.random() * symbolCount)|0];
}
return short;
}
Then, do your verification independently of this:
function assignShortForm(req, res) {
let short = makeShort();
verifyShortIsAvailable(
short,
success => {
// this short form was available
new Link({ url: req.body.url, short }).save().then(link => res.json(link));
}, error => {
// try again. RNG is not your friend, and this COULD run a very long time.
assignShortForm(req, res);
}
);
}
With your router using that function, not inlining it:
router.post('/', assignShortForm);
In this, verifyShortIsAvailable should do its work asynchronously:
verify verifyShortIsAvailable(short, resolve, reject) {
Link.findOne(query, (err, link) => {
if (err) return reject(err);
if (link) return reject("...");
return resolve();
});
}
while loops run synchronously, meaning they block the thread from further execution until they are complete. Because the link shortener is asynchronous, it is being blocked by the while loop.
To handle this code asynchronously, you can return a Promise
function makeShort() {
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
// note length was previously undefined in the comparison. use possible.length or another arbitrary value
for(var i = 0; i < possible.length; i++){
short += possible.charAt(Math.floor(Math.random() * possible.length));
}
let query = {short:short};
return new Promise((resolve, reject) => {
Link.findOne(query, (err, link) => {
if(err) return reject(err);
resolve(link)
});
})
}
Then you can use it like so...
let short = makeShort().then(shortLink => {
// do something with the link
}).catch(err => {
// handle the error
});
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises

Categories