This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 2 years ago.
With the following function I am trying to calculate the total price of a checkout. But if I console.log() the variable before it is returned, I get 0. If I console.log() the variable in the findOne() function, I get the correct value.
As database I use MongoDB and "Item" is a model.
function calculateOrderAmount(items) {
var totalPayment=0;
items.forEach((item) => {
Item.findOne( { _id: item } )
.then(item => {
if(item) {
// Item exists
totalPayment = item.price;
console.log(totalPayment);
}
});
});
console.log(totalPayment);
return totalPayment;
}
I'm desperate about it and I don't really know what to look for on the internet. Many thanks for answers in advance.
Item.findOne is an async operation, so in your code you execute:
var totalPayment = 0
items.forEach((item) => {...
console.log(totalPayment)
return totalPayment
other sync code who called calculateOrderAmount
then the callback of Item.findOne is run
You must use a callback sytle or an async function like this:
async function calculateOrderAmount (items) {
// beware: if you have a huge items list, doing so could lead to high memory usage
const itemsPromises = items.map((item) => {
return Item.findOne({ _id: item })
.then(item => {
if(item) {
// Item exists
return item.price;
}
return 0
});
})
const prices = await Promise.all(itemsPromises)
const totalPayment = prices.reduce((a, b) => a + b, 0)
console.log(totalPayment)
return totalPayment
}
Related
This question already has answers here:
Using async/await with a forEach loop
(33 answers)
How do I return the response from an asynchronous call?
(41 answers)
Closed 2 years ago.
I am trying to create a filtered array in an array of objects. I am doing so by running case switch in a forEach and deriving a new array attaching that to a new object and pushing that object to an array stored outside of the foreach. but after running the foreach the length of the external array still shows 0 and the rest of the equation relates to the processing of said array. Its two very large blocks of code so i've tried to redact some.
let updatedDrop = []
drop.forEach(async(tollFree) => {
const zipCodes = Object.values(tollFree)[0].split(",");
let updatedList = []
const {
tracking,
mailList,
} = tollFree;
zips = await Zip.find({
"class": { "$in": zipCodes },
});
zips = zips.map(zip => zip.zip4)
switch (zipCodeSuppress) {
case "keepSelect":
(insert case switch)
break;
}
const distinct = (value, index, self) => {
return self.indexOf(value) === index;
};
updatedList = updatedList.flat()
updatedList = updatedList.filter(distinct)
const combinedCost = unitCost + postageCeiling
const dropItem = {
updatedList,
tracking,
}
updatedDrop.push(dropItem)
//console.log(dropItem)
})
console.log(updatedDrop.length)
let advJobs = [];
let cspJobs = [];
let wbJobs = [];
if (updatedDrop.length > 0){ .....
so until i am able to access the updated asynchronous data the rest of the formula is stalled. How do I do this?
The problem you are facing is that the forEach callback doesn't block the main thread, so at the moment you access the array right after the declaration of the forEach, the callback inside didn't finish executing.
Look at this example
const timer = 2000;
const numbers = [1, 2, 3];
const emptyArray = [];
async function getNumberTwo() {
return new Promise((resolve) => {
setTimeout(() => resolve(2), timer);
});
}
async function withForEach() {
numbers.forEach(async (n) => {
const two = await getNumberTwo();
emptyArray.push(n + two);
});
console.log(emptyArray); // the array is empty here.
setTimeout(() => console.log(emptyArray), numbers.length * timer); // if I log the array after all the time has gone, the array has the numbers.
}
withForEach()
but now if you use for of, or even a normal for I would say
// all the declarations from before..
async function withForOf() {
for (const number of numbers) {
const two = await getNumberTwo();
emptyArray.push(number + two);
}
console.log(emptyArray); // now the array has what you expect it to have
}
withForOf()
So, in conclusion, you can use a normal for or a for of to make it work as you need.
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 2 years ago.
I am very new to JavaScript, trying to understand the concepts of asynchronous function. So, basically I wrote a webScraper for RateMyProfessor using nightmare. This is the function:
var Nightmare = require("nightmare"),
nightmare = Nightmare();
const baseURL =
"https://www.ratemyprofessors.com/search.jsp?queryBy=schoolId&schoolName=University+of+California+Santa+Barbara&schoolID=1077&queryoption=TEACHER";
const getRatingProfessor = (professorName) => {
let rating;
let nameSplitter = professorName.split(" ");
if (nameSplitter.length > 2) {
professorName = nameSplitter[0] + " " + nameSplitter[1];
}
nightmare
.goto(baseURL)
.click("#ccpa-footer > .close-this")
.type("#professor-name", professorName)
.wait(1000)
.evaluate(() => {
var resultList = document.querySelectorAll("a .rating");
//if no result is found
if (typeof resultList === "undefined" || resultList.length == 0) {
return "Not Found";
}
//Found the professor with exact name
if (resultList.length == 1) {
return document.querySelector("a > .rating").innerHTML;
}
//conficting similar professor names (rare case)
if (resultList.length >= 2) {
return "Cannot Determine";
}
})
.end()
.catch((err) => {
console.log(err);
})
.then((text) => {
rating = text;
console.log(professorName, text);
});
return rating;
};
console.log(getRatingProfessor("XXXXX"));
If I run this program, it gives the following output:
undefined
SomeProfName 4.8
It seems like the function returned the rating to console.log without waiting for the promise. Why isn't function not waiting for the nightmare promise to get resolved. More importantly, why isn't the value of rating getting updated; or it has been updated but console.log doesn't want to wait for the function?
Sorry, these questions may look absurd, but I would really appreciated the answers :0
Your function explicitly does not return anything hence will return undefined by default. What you are getting is correct but within the then()...
If you wanted to return an async result you'd need to declare your function as async and then await the result.
const getRatingProfessor = async (professorName) => {
let rating;
let nameSplitter = professorName.split(" ");
if (nameSplitter.length > 2) {
professorName = nameSplitter[0] + " " + nameSplitter[1];
}
await text = nightmare...
return text;
};
(async () => {
console.log(await getRatingProfessor("XXXXX"));
}
)()
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
How to return an Ajax result using async/await? [duplicate]
(3 answers)
Closed 2 years ago.
I'm not getting the return value I'm expecting from an async function when I try to save the return value in a variable. When I step into my function and inspect the return value with the debugger statement (an array of numbers representing categories i.e. [123, 423, 874, 999, 234, 452]) it is what I expect it to be.
I am using the run() function as that I'm using as a wrapper for when I call the getRandomCategories() function. When I console.log(res) it is an array of ids (this is what I'm expecting)
But when I try to save the return value in a variable (const categoriesArray = run()) I'm expecting an array of ids so I can use the array for another function instead I'm getting a Promise with a pending state. What am I missing?
Here's my code:
async function getData(endpoint, query, value) {
const res = await axios.get(
`http://jservice.io/api/${endpoint}?&${query}=${value}`
);
return res;
}
// createa a function that will return 6 random categories
async function getRandomCategories() {
try {
const res = await getData('categories', 'count', 50);
const data = res.data;
const categories = filterCategoryData(data); // I'm filtering for category id with clues_count === 5
const categoryIdArr = mapCategoryIds(categories); // an array of just category Ids
const shuffledCategoryIds = shuffle(categoryIdArr);
const apiCallCategoryArray = takeFirstXItems(shuffledCategoryIds, 6);
return apiCallCategoryArray;
} catch (err) {
console.log(err);
}
}
async function run() {
const res = await getRandomCategories()
// console.log(res) logs my expected output
return res // I want it to return an array of numbers.
}
const categoriesArray = run() // I'm expecting and array of ids
console.log(categoriesArray) // Why am I not gettng an array of ids in
//my variable? Instead I get a Promise: state <pending>
Since run() returns a Promise as the questioner discovered, either await for the resolved value or attach a then handler as follow.
run().then(categoriesArray => console.log(categoriesArray));
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 4 years ago.
I'm developing a google chrome extension and I have to loop trough the nodes (folders) to check how many items I have within each folder. I'm suppling an item ID to the function getBookmarksCount(ID). I'm having an issue to get a result from the main function the console.log() returns correct value at the point of logging.
Here is my code:
const getBookmarksCount = (bmkNode) => {
let nodes = []
let result = 0
new Promise ((resolve, reject) => {
chrome.bookmarks.getChildren(bmkNode, (bmkChildren) => {
_.each(bmkChildren, (item) => {
// Check if the item is a bookmark link
if (!(item.url === undefined || item.url === null)) {
nodes.push(item.title)
}
})
resolve(_.size(nodes))
})
}).then((size) => {
console.log(size) //The correct number of items is listed here eg. 6
result = size
})
return result
}
//I'm suppling a parent folder ID the function should return number of children
getBookmarksCount(123) // eg. 6 -> at the moment returns 0
Here is my updated working version without Promise. setTimeout() is a dirty hack but works. Any suggestions how I can improve this function?
const getBookmarksCount = (bmkNode) => {
let nodes = []
const getChildrenCount = (bmkNode) => {
chrome.bookmarks.getChildren(bmkNode, (bmkChildren) => {
_.each(bmkChildren, (item) => {
// if is bookmark otherwise loop trough subfolder
(!(item.url === undefined || item.url === null)) ? nodes.push(item.title): getChildrenCount(item.id)
})
})
setTimeout(() => {
$(`#counter_${bmkNode}`).html(_.size(nodes))
}, 50)
}
getChildrenCount(bmkNode)
}
// HTML Template
<label class="label label-primary" id="counter_${item.id}">0</label>
As pointed out by Bravo in the comments, you are not really waiting for your code to execute. You are ever so close, though!
const getBookmarksCount = (bmkNode) => {
return new Promise ((resolve) => {
let nodes = []
chrome.bookmarks.getChildren(bmkNode, (bmkChildren) => {
_.each(bmkChildren, (item) => {
// Check if the item is a bookmark link
if (!(item.url === undefined || item.url === null)) {
nodes.push(item.title)
}
})
resolve(_.size(nodes))
})
})
}
getBookmarksCount(123).then(size => {
console.log(size)
})
Note the return new Promise on the second line, which is a key difference from your provided snippet. By doing this, you wait to "return" here until you actually finish the async work and call resolve. Then, to get the value returned, you would do the same .then syntax you used but at the call of getBookmarksCount.
Hopefully that helps and, of course, also works!
This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Closed 5 years ago.
Currently we are storing short strings as keys.
These keys correspond to long values which are labels.
I am trying to update the corresponding long value for the key.
But the console.log(record) always executes first and then the inner log statement executes which is not is desired. It always sends the unmodified record to the getRadioValues function caller.
I want to return the record after the corresponding key is updated.
export const getRadioValues = (record: IRecordInput) => {
const singleSelectKeys = ['Race', 'DeathWas', 'MannerOfDeath'];
singleSelectKeys.forEach(async key => {
if (record[key]) {
const dropDownOption = await DropDownOptions.find({ where: { id: record[key] }}) as IPDFSelect;
record[key] = dropDownOption.dataValues.Text;
console.log(record[key]);
}
});
console.log(record);
return record;
};
Your forEach is using an async function which means that the loop probably finishes before any of the promises it created do. To fix this, you need to get the result of your promises and wait on them. However, this means the enclosing function must itself be async, which may or may not be acceptable for your actual use case.
export const getRadioValues = async (record: IRecordInput) => {
const singleSelectKeys = ['Race', 'DeathWas', 'MannerOfDeath'];
await Promise.all(singleSelectKeys.map(async key => {
if (record[key]) {
const dropDownOption = await DropDownOptions.find({ where: { id: record[key] }}) as IPDFSelect;
record[key] = dropDownOption.dataValues.Text;
console.log(record[key]);
}
}));
console.log(record);
return record;
};