Using While loop inside async function? - javascript

I'm trying to use a while loop inside an async function, and I suspect my resolve(); is messing up the loop, but I'm not sure what to do about it. Here's the code I have:
app.get("/meal-plan", async function(req, res) {
var calorieCount = req.query.calorieCount;
var mealInput = parseInt(req.query.mealInput);
var dietInput = req.query.food;
var goalPerMeal = (calorieCount / mealInput);
var whichDiet = "diet." + dietInput;
var breakfastGoal = goalPerMeal;
var breakfastArr = [];
async function getBreakfastArr() {
return new Promise((resolve, reject) => {
var breakfastQuery = {"breakfast": true, [whichDiet]: true};
while (breakfastGoal >= 150) {
Food.count(breakfastQuery, function(err, count) {
if (err) {
console.log(err);
} else {
var random = Math.floor(Math.random() * count);
Food.findOne(breakfastQuery).skip(random).exec(
function(err, result) {
if (err) {
console.log(err);
} else {
breakfastGoal -= result.nutrition.calories;
breakfastArr.push(result);
resolve();
}
});
}
})
}
});
}
try {
await getBreakfastArr();
console.log(breakfastArr);
res.render("meal-plan.ejs", { meal: mealInput, calories: calorieCount, diet: dietInput, breakfast: breakfast, lunch: lunch, dinner: dinner, totalCalories: totalCalories});
} catch (e) {
res.json(e);
}
});
The goalPerMeal variable takes a user calorie input and divides it by the number of meals they select on my form. I then set that value to a specific breakfast variable called breakfastGoal. The async function finds a random recipe from my database and adds it to my array, breakfastArr. Once it finds the recipe, it subtracts that recipe's calorie count from the breakfastGoal.
I want this function to run until breakfastGoal is reduced to below 150; however, it doesn't seem to work. If I remove the while loop, the function will successfully find the breakfast item, add it to the array, and subtract its calorie count from breakfastGoal. The only thing breaking it is adding the while loop.
Is this something to do with the resolve(); in the async function, or am I missing something important?
Any help is extremely appreciated.

Do you think something like this will work? I have not tested this. Just an idea.
put the while block inside the else block and call recursively.
async function getBreakfastArr() {
let breakfastQuery = { "breakfast": true, [whichDiet]: true };
return this.getRandomRecipe(breakfastQuery).then((response)=>{
return response; //the response will have your breakfast goal
});
}
async function getRandomRecipe(breakfastQuery) {
return Food.count(breakfastQuery, function (err, count) {
if (err) {
console.log(err);
} else {
var random = Math.floor(Math.random() * count);
Food.findOne(breakfastQuery).skip(random).exec(
function (err, result) {
if (err) {
console.log(err);
} else {
breakfastGoal -= result.nutrition.calories;
breakfastArr.push(result);
while (breakfastGoal >= 150) {
this.getRandomRecipe(breakfastQuery);
}
return Promise.resolve(breakfastGoal);
}
});
}
})
}

The problem is that you create only one promise, but have several asynchronous results you need to wait for in the while loop.
The first time resolve is called, that sole promise is resolved, and that will make the code following the await to execute. But at that time your array is not finished at all. Also, any further calls to resolve will not influence that promise any more: a promise can only be resolved once. The other calls are ignored.
The solution is to make a promise in each iteration of the while loop, and await it.
Change this:
return new Promise((resolve, reject) => {
var breakfastQuery = {"breakfast": true, [whichDiet]: true};
while (breakfastGoal >= 150) {
to this:
var breakfastQuery = {"breakfast": true, [whichDiet]: true};
while (breakfastGoal >= 150) {
await new Promise((resolve, reject) => {
The code could be improved more, but it is not relevant to your question. For instance, it would be better to make each promise resolve with the value to be added to the array breakfastArr, so that you would have:
breakfastArr.push(await new Promise((resolve, reject) => {
// ...
resolve(result)
// ...
});
And the async function should return that array, so that it does not have to be defined at the top.

The issue here is that you are mixing callback taking functions with promises. The proper way to do this is to wrap only the callback taking functions in promise constructors. The result is some thing like this:
app.get("/meal-plan", async function(req, res) {
var calorieCount = req.query.calorieCount;
var mealInput = parseInt(req.query.mealInput);
var dietInput = req.query.food;
var goalPerMeal = (calorieCount / mealInput);
var whichDiet = "diet." + dietInput;
var breakfastGoal = goalPerMeal;
function count(query) {
Food.count(query, function(err, result) {
if (err) {
reject(error)
} else {
resolve(result)
}
});
}
function findOne(query, count) {
return new Promise((resolve, reject) => {
Food.findOne(query).skip(count).exec(function(err, result) {
if (err) {
reject(error)
} else {
resolve(result)
}
});
});
}
async function getBreakfastArr() {
let breakfastQuery = {
"breakfast": true,
[whichDiet]: true
};
let breakfastArr = [];
while (breakfastGoal >= 150) {
let count = await count(breakfastQuery);
let random = Math.floor(Math.random() * count);
let result = await findOne(breakfastQuery, random);
breakfastGoal -= result.nutrition.calories;
breakfastArr.push(result);
}
return breakfastArr
}
try {
let breakfastArr = await getBreakfastArr();
console.log(breakfastArr);
res.render("meal-plan.ejs", {
meal: mealInput,
calories: calorieCount,
diet: dietInput,
breakfast: breakfast,
lunch: lunch,
dinner: dinner,
totalCalories: totalCalories
});
} catch (e) {
res.json(e);
}
});
Some of the Promise libraries had a function called promisify that would take a function taking a callback as its last argument which took node style (err, result) arguments and produced a function that when called returned a promise. If that is available the wrappers get a lot smaller. for example the Food.count wrapper becomes let count = promisify(Food.count); orlet count = promisify(Food.count.bind(Food));

You are returning a promise from async function which is not required. You can remove async from the function returning promise.
So I suggest removing async from function getBreakfastArr()

Related

async parallelLimit runs limit times only once

I am trying to make use of async.parallelLimit to update records in my db. I need to do this in a batches of 10 and each time the total records will be 1000. I am trying to understand how can i make use of this asyn.parallelLimit in my code. I looked at some example interpreation and tried but it is running only 10 times and after giving back 10 responses it does not give me next ones. I am trying to query the db by executing the filter and then sending the records into async.parallelLimit and get response of all those records. For now i am just trying to understand the parallelLimit and its working. Here is my code
const async = require("async");
module.exports = async (server) => {
async function data(server) {
return await resolveData(server);
}
function resolveData(server) {
return new Promise((resolve) => {
let parcel = server.models["Parcel"].find({
order: "createdAt ASC",
include: [
{
relation: "parcelStatuses",
},
{
relation: "customerData",
scope: {
fields: ["firstName", "lastName", "cityId", "customerId"],
},
},
],
limit: 1000,
skip: 200,
});
resolve(parcel);
});
}
// console.log(await data(server));
var parcelData = await data(server);
for (var i = 1; i <= parcelData.length; i++) {
parcelData[i - 1] = (function (i) {
return function () {
console.log(i);
};
})(parcelData[i]);
}
async.parallelLimit(parcelData, 10, function (err, results) {
if (results) {
console.log("This is resilt", results);
} else {
console.log(err);
}
});
};
I need my parallelLimit function to just return me the records that my query fetch. I will run the update commands later. Since its returning me only 10 records i want to know what i am doing wrong
I'm not sure what the for loop is supposed to do. I'm guessing you're trying to create functions for each elements of the parcelData. An easy way to do that is just map over the parcelData and return an async function:
let async = require("async");
// Your data is here but I used a dummy array with 100 elements
let parcelData = Array(100).fill(0).map((_, i) => i);
// Just a dummy function to wait
function delay(ms) {
return new Promise(r => setTimeout(r, ms));
}
// map over array and return an `async` function;
let tasks = parcelData.map(p => {
return async () => {
await delay(1000);
console.log(p);
}
});
// Finally execute the tasks
async.parallelLimit(tasks, 10, (err, result) => {
if (err) console.error('error: ', err)
else console.log('done');
});
You can run the code on repl.it to see it in action. Here's one I made.

problems with asynchronous data recording

there is a function code:
function fillingDataInCategory(categories, url) {
let categoriesData = [];
for (let i = 0; i < categories.length; i++) {
conn.query(`SELECT product_id
FROM products
WHERE product_category_id = ${categories[i].id}`, (err, productInCategory) => {
if(err) {
console.log(err);
fs.writeFileSync('api-reports-error-log.txt',
`${fs.readFileSync('api-reports-error-log.txt')}\n${url}: ${err} ${new Date().toLocaleDateString()}`);
} else {
console.log(productInCategory);
categoriesData.push({category: categories[i].id, productInCategory: productInCategory.length});
}
});
}
}
The problem is that an empty categoriesData array is returned due to asynchronous writing.
I haven't worked with asynchrony much, so I'll be happy to get any help.
I don't see a return at the end of your function, but I assume you want to return categoryData
I think that you are currently calling your function like this:
function myFunc() {
// Do stuff
let categoriesData = fillingDataInCategory(categories, "myurl");
console.log(categoriesData);
// Do stuff
}
First of all, I advise you to use forEach instead of for();
I suggest you to use a promise. And as you do multiple query in one function, use Promise.all
If the library you use to make your mysql calls allows it, use promises directly, but if it only allows you to make callbacks, convert that callback to a promise like this:
function makeRequest(categorie) {
return new Promise((resolve, reject) => {
conn.query(`SELECT product_id FROM products WHERE product_category_id = ${categorie.id}`, (err, productInCategory) => {
if (err) {
reject(err) //reject at error
} else {
resolve({category: categorie.id, productInCategory: productInCategory /* You can put your .length here */}); //resolve with your result
}
});
});
}
Here are the code that i wrote for you. I simulate your call do conn.query() with a delay of 1 sec
const fs = require('fs');
/* SIMULATE conn.query async call */
let conn = {
query: (text, callback) => {
setTimeout(callback(null, [1, 2, 3, 4] /* Return an fake array of result of length 4 */), 1000);
}
}
function makeRequest(categorie) {
return new Promise((resolve, reject) => {
conn.query(`SELECT product_id FROM products WHERE product_category_id = ${categorie.id}`, (err, productInCategory) => {
if (err) {
reject(err) //reject at error
} else {
resolve({category: categorie.id, productInCategory: productInCategory.length}); //resolve with your result
}
});
});
}
function fillingDataInCategory(categories, url) {
return new Promise((resolve, reject) => {
let promiseList = [] //Make a array to fill with pending promise
categories.forEach((categorie) => {
promiseList.push(makeRequest(categorie)); // Adding a new pending request with the categorie inside the array
})
resolve(Promise.all(promiseList)); // Promise.all return a promise that take a list of pending promise
});
}
function myFunc() {
fillingDataInCategory([{id: 1}, {id: 2}, {id: 3}], "myurl").then((categorieData) => {
console.log(categorieData); //Now you get the resolve of the Promise.all
//Do your post work
}).catch((err) => { // If one of all the requests of the Promise.all array throw an error, then all requests fails
//Do error stuffs
});
// Do stuff
}
myFunc();
and i get this :
➜ node test-stackoverflow.js
[
{ category: 1, productInCategory: 4 },
{ category: 2, productInCategory: 4 },
{ category: 3, productInCategory: 4 }
]

Nedb async function to be awaited

async function count() {
let nedb = <<functional nedb database>>
let count = 0
await iterate(nedb, {}, g=>{
count++
})
console.log(count)
iterate(xx, query, callback) {
return new Promise((res, rej)=> {
pkgs.find(query).exec((err, res)=>{
if(err!==null) {
this.err(err)
} else {
res.forEach((pkg)=>{
callback(pkg)
})
}
})
res()
})
I would like to write something, after the iteration is over, is there way to do it in some normal way?
You need to put the res()olve call inside the find callback. Immediately fulfilling the promise to undefined won't help.
But you really shouldn't pass an iteration callback at all. Instead, write
async function count() {
let nedb = …;
let count = 0;
const pkgs = await query(nedb, {});
pkgs.forEach(pkg => count++);
// or maybe better just:
const count = pkgs.length;
console.log(count);
}
function query(pkgs, query) {
return new Promise((resolve, reject)=> {
pkgs.find(query).exec((error, result)=>{
if (error !== null) reject(error);
else resolve(result);
});
});
}

How to recover a result of query mongodb outside of its function?

I am a new javascript user and I am looking for a rescuer the result (count) of a memory request.
More precisely, maybe you can comment, put my variable in my conv object
right now my result is [promised object]
I tried with async and promised javascript but I do not understand the logic yet
Thank you so much,
Gabriel
var conversations = []
for (var i = 0; i < conversation.length; i++) {
for (var j = 0; j < conversation[i].users.length; j++) {
conv = conversation[i].users[j]
async function asyncCall() {
function countUnread() {
filterUnread = {$and:[
{toID: user.properties.chat.id},
{fromID: conversation[i].users[j].id},
{ read: false }]}
return new Promise(resolve => {
Message.countDocuments(filterUnread, function (err, count) {
resolve(count)
})
})
}
var count = await countUnread();
console.log(count);
console.log(conv)
resolve(!!!!count!!!!) ;
}
asyncCall();
conv.unread = !!!!I_want_my_count_here!!!!
conversations.push(conv);
resolve(conversations)
}
}
Mixing async functions and Promise constructors makes the code hard to track and causes problems like this one. Instead only wrap the most low level parts into Promise constructors, let tuem resolve to something useful, then compose multiple such Promises using async :
const filter = { users: { $elemMatch: { id: user.properties.chat.id } } }
function getConversations(filter) {
return new Promise(function(resolve, reject) {
Conversation.find(filter, function(err, conversations) {
if(err) reject(err) else resolve(conversations);
});
});
}
function countUnread(user) {
const filterUnread = { $and:[
{toID: user.properties.chat.id},
{fromID: user.id},
{ read: false }
]};
return new Promise((resolve, reject) => {
Message.countDocuments(filterUnread, function (err, count) {
if(err) reject(err) else resolve(count);
});
});
}
async function composeConversations() {
const conversations = await getConversations();
const result = [];
for(const conversation of conversations) {
for(const user of conversation.users) {
const count = await countUnread(user);
user.unread = count;
}
result.push(conversation);
}
return result;
}

nodejs use gm module to process the images but I need it sync

here are two method .but i need it to finish sync.But it is failed.
I want to use the Promise.all to keep it sync but i find the images process is async and it can't reject or resolve in a loop! It really makes me confused .
below is my code.I can't catch the res or err. it just end after processed the images...even don't excute the test Method,but i wonder that Promise.all is a sync method? And is there any way to catch the res or err information? Thanks !
var gm = require('gm')
var fs = require('fs')
var config = require('./imgConfig')
var image = ['1.jpg', '2.jpg', '3.jpg', '4.jpg','5.jpg','6.jpg']
var _image = ['_1.jpg', '_2.jpg', '_3.jpg', '_4.jpg','_5.jpg','6.jpg']
testS()
function testS() {
uploadFiles(image, _image, 'subModule').then(res => {
console.log(res)
}).catch(e => {
console.log(e)
})
}
function uploadFiles(inputArray, outputArray, type) {
return Promise.all([multipleResize(inputArray, outputArray, type), test()])
}
function test() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("object");
resolve("yeah")
}, 100)
})
}
function multipleResize(inputArray, outputArray, type) {
var height = config[type].height;
var width = config[type].width;
return Promise.all(inputArray.map((img, index) => {
return new Promise((resolve, reject) => {
gm(config.inputPath + img)
.resizeExact(width, height)
.noProfile()
.write(config.outputPath + outputArray[index], function (err) {
if (err) {
console.error(err);
return reject(err);
}
try {
fs.unlinkSync(config.inputPath + img); // consider using an async method here as well.
console.log("next" + config.outputPath + outputArray[index]);
console.log('ko');
return resolve();
} catch (e) {
console.error(e);
reject(e);
}
});
});
}));
}
imgConfig
module.exports = {
'subModule': {
width: 300,
height: 300
},
inputPath:'./input/',
outputPath:'./output/'
}
In multipleResolve, you are running return resolve() as part of the #forEach() call. This probably isn't what you want, as the first iteration will resolve the promise you returned, even though all of your gm .write() calls won't be finished yet.
I would consider doing something like this -- note the internal use of Promise.all() and the inputArray#map() to convert each img/index pair to their own Promise. I also swapped around some of the error logic to make sure the errors are checked as soon as possible.
function multipleResize(inputArray, outputArray, type) {
var height = config[type].height;
var width = config[type].width;
var flag = 0;
return Promise.all(inputArray.map((img, index) => {
return new Promise((resolve, reject) => {
gm(config.inputPath + img)
.resizeExact(width, height)
.noProfile()
.write(config.outputPath + outputArray[index], function (err) {
if (err) {
console.error(err);
return reject(err);
}
try {
fs.unlinkSync(config.inputPath + img); // consider using an async method here as well.
console.log("next" + config.outputPath + outputArray[index]);
flag++;
console.log('ko');
return resolve();
} catch (e) {
console.error(e);
reject(e);
}
});
});
}));
}
Side note, you seem to be using promises (mostly) correctly, but you are incorrectly calling them sync, when they typically aren't synchronous.

Categories