expressJs - Wait for a promise inside a loop - node.js

Novice question; I need to create a conditional loop which makes a call to the database. The value returned from the promise determines if I should break out of the loop as follows:
let id, caged;
do {
caged = false;
id = models.Cage.generateId();
caged = models.Cage.findOne({ where: { dispatchLabelId: id } });
} while( caged );
Can anybody please advise how I can structure my code?

You can do that using promises and recursive function calls:
let getCage = () => {
return models.Cage
.generateId()
.then(id => models.Cage.findOne({ where: { dispatchLabelId: id } }))
.then(caged => {
if (caged) {
return caged;
}
return getCage();
});
}
In the case if you can use node.js v7.6 or higher you can avoid using recursive function calls and implement this using async/await:
let getCage = async () => {
try {
do {
let id = await models.Cage.generateId();
let caged = await models.Cage.findOne({ where: { dispatchLabelId: id } });
if (caged) {
return caged;
}
} while (true);
} catch (err) {
console.log(err);
}
}

Related

async/await troubles in a recursive Redis function

Ima rookie using async/await but must now to use Redis-om. NN_walkd walks through a Redis database looking for loop-chains and does this by recursion. So the 2 questions I have is:
Am I calling the inner recursive NN_walkd calls correctly via async/await?
At runtime, the compSearchM proc is called first and seems to work (it gets 5 entries so it has to call NN_walkd 5 times). A NN_walkd is then recursively called, and then when it loops the 1st time it then calls compSearchK where the problems are. It seems to sit on the first Redis call in compSearchK (.search). Yet the code for compSearchK and compSearchM look basically identical.
main call
NN_walk = async function(req, db, cnode, pnode, chain, cb) {
var vegas, sneaker;
req.session.walk = [];
await NN_walkd(req, cnode, pnode, [], 1);
req.session.walk = null;
console.log('~~~~~~~~~~~~ Out of Walk ~~~~~~~~~~~~~~~');
cb();
};
redis.mjs
export class RedisDB {
constructor() {
...
this._companyRepo = ...
}
compSearchK(ckey) { // doesn't matter if I have a async or not here
return new Promise(async (resolve) => {
const sckey = await this._companyRepo.search()
.where('COMPANYKEY').equals(ckey)
.return.all();
if (sckey.length) {
const ttrr = await this._companyRepo.fetch(sckey[0].entityId);
resolve(ttrr.toJSON());
} else
resolve(null);
});
}
compSearchM(mkey) {
var tArr=[];
return new Promise(async (resolve) => {
const smkey = await this._companyRepo.search()
.where('MASTERKEY').equals(mkey)
.and('TBLNUM').equals(10)
.return.all();
if (smkey.length) {
for (var spot in smkey) {
const ttrr = await this._companyRepo.fetch(smkey[spot].entityId);
tArr.push(ttrr.toJSON());
}
resolve(tArr);
} else {
resolve(null);
}
});
}
walk.js
NN_walkd = async function(req, cnode, pnode, chain, lvl) {
...
if (cnode[1]) {
const sObj = await req.app.get('redis').compSearchK(cnode[1]);
if (sObj) {
int1 = (sObj.TBLNUM==1) ? null : sObj.CLIENTKEY;
(async () => await NN_walkd(req, [sObj.COMPANYKEY,int1], cnode, Array.from(chain), tlvl))()
}
} else {
const sArr = await req.app.get('redis').compSearchM(cnode[0]);
if (sArr.length) {
for (sneaker in sArr) {
(async () => await NN_walkd(req, [sArr[sneaker].COMPANYKEY,sArr[sneaker].CLIENTKEY], cnode, Array.from(chain), tlvl))()
}
} else {
console.log('no more links on this chain: ',cnode);
}
}
}
"doesn't matter if i have async or not here"
compSearchK(ckey) { // doesn't matter if I have a async or not here
return new Promise(async (resolve) => {
const sckey = await this._companyRepo.search()
.where('COMPANYKEY').equals(ckey)
.return.all();
if (sckey.length) {
const ttrr = await this._companyRepo.fetch(sckey[0].entityId);
resolve(ttrr.toJSON());
} else
resolve(null);
});
}
Of course it doesn't matter, because you're not using await inside of compSearchK!
You are using the explicit promise contructor anti-pattern. You should avoid it as it demonstrates lack of understanding. Here is compSearchK rewritten without the anti-pattern -
async compSearchK(ckey) {
// await key
const sckey =
await this._companyRepo.search()
.where('COMPANYKEY').equals(ckey)
.return.all();
// return null if key is not found
if (sckey.length == 0) return null;
// otherwise get ttrr
const ttrr = await this._companyRepo.fetch(sckey[0].entityId);
// return ttrr as json
return ttrr.toJSON();
}

Issue with useMutation with await and promise

I am performing the useMutation operation in the innermost loop and want to check the remaining cost upon every mutation. But it gets checked after all the mutations which is a problem because for some reason even if all the mutations get done(When the cost is under limits), It calls the .then() part for cost-checking and waiting for unknown reason.
Edit: I also noticed that even though the program is waiting again and again, the network status of chrome shows that all the mutations have happened and only the query of handleDiscountMore i.e. fetchMore is pending
const { loading, error, data, fetchMore, extensions, refetch } = useQuery(GET_COLLECTION, {
variables: { "id": coll.collection.id }
});
const [updatePrice] = useMutation(UPDATE_PRICE);
const redirectToModify = async (data, totalProducts) => {
wait(20000);
var cursor, fetchCount;
fetchCount = data.collection.products.edges.length;
totalProducts -= fetchCount;
data.collection.products.edges.map(async(product) => {
const results = await Promise.all(product.node.variants.edges.map(variant => {
if (selected == 'curr_price') {
//do stuff
}
else {
//do stuff
}
const productVariableInput = {
//Object
};
updatePrice({ variables: { input: productVariableInput } }).then(({ data, extensions }) => {
console.log("Remaining", extensions.cost.throttleStatus.currentlyAvailable)
console.log(data)
if (extensions.cost.throttleStatus.currentlyAvailable < 100) {
console.log("WAITING")
wait(18000);
}
}).catch(e => {
console.log(e)
})
console.log("AFTER")
return 0;
}))
})
if (totalProducts > 0) {
console.log("Calling")
wait(15000);
handleDiscountMore(data, cursor, totalProducts)
}
};
//Below function is Just for reference. It gets called before checking the throttleStatus above. afaik there's no problem with this
const handleDiscountMore = (data, cursor, pc) => {
console.log("Call received")
fetchMore({
variables: {
"id": data.collection.id,
"cursor": cursor
},
updateQuery: (
previousResult,
{ fetchMoreResult }
) => {
console.log("adding", fetchMoreResult);
redirectToModify(fetchMoreResult, pc);
// return fetchMoreResult;
}
})
}
Your map of maps is evaluating all promises at exactly the same time. Here's a cleaned up example that uses a nested for loop instead, which will wait for each request to finish before starting the next (note: I couldn't run it to test, so there's probably some bugs, but the idea is there):
const id = coll.collection.id;
const { loading, error, data, fetchMore, extensions, refetch } = useQuery(GET_COLLECTION, {
variables: { id }
});
const [updatePrice] = useMutation(UPDATE_PRICE);
// Given a product, returns a promise that resolves when all variants are processed
async function process_product(product){
const variants = product.node.variants.edges;
for (let i = 0; i < variants.length; i++){
await process_variant(variants[i]);
}
}
// Given a variant, returns a promise after the product is processed
async function process_variant(variant){
if (variant) {
console.log('doing stuff')
}
else {
console.log('doing other stuff')
}
const productVariableInput = {};
const variables = { input: productVariableInput };
try {
const {data, extensions} = await updatePrice({ variables });
const remaining_throttle = extensions.cost.throttleStatus.currentlyAvailable;
console.log("Remaining", remaining_throttle)
console.log(data)
// Change to a while loop to make sure you actually wait until resources are available
if (remaining_throttle < 100) {
console.log("WAITING")
await wait(18000);
}
} catch (e) {
console.log('error:', e);
}
console.log("AFTER")
return 0;
}
const redirectToModify = async (data, totalProducts) => {
await wait(20000);
let cursor;
const products = data.collection.product.edges;
totalProducts = totalProducts - products.length;
// Wait for all products to finish processing
for (var i = 0; i < products.length; i++){
await process_product(products[i]);
}
if (totalProducts > 0) {
console.log("Calling")
await wait(15000);
handleDiscountMore(data, cursor, totalProducts)
}
};
function updateQuery(previousResult, { fetchMoreResult }){
console.log("adding", fetchMoreResult);
redirectToModify(fetchMoreResult, pc);
return fetchMoreResult;
}
//Below function is Just for reference. It gets called before checking the throttleStatus above. afaik there's no problem with this
function handleDiscountMore(data, cursor, pc) {
console.log("Call received")
const variables = { id: data.collection.id, cursor };
fetchMore({ variables, updateQuery })
}

NodeJS - Nested Promise inside a for loop

I am trying to do a call which retrieves a list of categories. Inside this call I want to loop through the categories and retrieve the items for each category and return them all together. My call retrieves the categories perfectly before I added the loop to retrieve the items.
To double check my call to another controller works, I added a proof of concept block of code which you can see below is commented out. So I know it isn't the call to an external class.
Here is my code:
'use strict';
var mongoose = require('mongoose'),
MenuCategory = mongoose.model('MenuCategory');
module.exports = function(menuItemController) {
var mod = {
listEntireMenu(req, res) {
return new Promise(function(resolve, reject) {
var entireMenu = [];
MenuCategory.find({}, function(err, menuCategories) {
if (err) {
return reject(err)
} else {
//---------------------------
// PROOF OF CONCEPT THAT CALL TO OTHER CONTROLLER WORKS
//---------------------------
//
// var categoryWithItems = menuCategories[0].toObject();
// req.body.menuCategoryID = categoryWithItems._id;
// menuItemController.listAllMenuItemsByCategory(req, res).then((menuItems) => {
// if(menuItems)
// {
// return resolve(menuItems);
// }
// else
// {
// return { success: false }
// }
// });
//-----------------------------
for (var i = 0; i < menuCategories.length; i++) {
var categoryWithItems = menuCategories[i].toObject();
var subItems = [];
req.body.menuCategoryID = categoryWithItems._id;
menuItemController.listAllMenuItemsByCategory(req, res).then((menuItems) => {
if(menuItems)
{
subItems = menuItems;
}
else
{
return { success: false }
}
});
categoryWithItems.tester = { "itemsList" : subItems };
entireMenu.push(categoryWithItems);
}
return resolve(entireMenu)
}
});
}).then((menuCategories) => {
if(menuCategories)
{
return menuCategories
}
else
{
return { success: false }
}
});
},
}
return mod;
};
What I actually get returned is this :
[
{
"_id": "5ed16fxxxxxxxx95676e37",
"locationID": "5ed16xxxxxxxx7295676e36",
"menuCategoryName": "Category One",
"Created_date": "2020-05-29T20:26:34.991Z",
"__v": 0,
"tester": {
"itemsList": []
}
},
{
"_id": "5ed170xxxxxx95676e38",
"locationID": "5ed16xxxxxxxx7295676e36",
"menuCategoryName": "Category Two",
"Created_date": "2020-05-29T20:26:48.799Z",
"__v": 0,
"tester": {
"itemsList": []
}
}
]
Here is the call from the route.js :
app.get('/api/listEntireMenu', (req, res) => {
menuCategoryController.listEntireMenu(req, res).then(menuCategories => res.json(menuCategories));
})
It never writes the subItems into the object. Is this an async issue or something else? I am not sure how to solve this.
Thanks in advance.
i believe the reason the result of your call to resolve is being returned before the requests are able to complete...for this you need to wait until all the promises or requests have finished properly and returned.
There are two ways you can do this: you could either run them one by one and wait for each one to finish first or run them all concurrently until all of them are done.
Ofcourse the fastest way to do it would be to run them all concurrently so lets go for that way:
so to start, let us not use the for loop and instead remap the iterable array menuCategories to promises of the request, we will use your proof of concept code to make the array of promises
//...
Promise.all(
menuCategories.map((category) => {
let category_with_items = category.toObject();
req.body.menuCategoryID = category_with_items._id;
// here we need to return this since its the promise we are remapping to
return menuItemController.listAllMenuItemsByCategory(req, res)
.then((menuitems) => {
if(menuItems) {
return menuitems;
}
throw 'No menu items found'
});
});
)
// each promise will return menuitems so we have to wait for all the promises to complete
// then with the results of each promise we push the items into the entire menu
.then((itemslist) => {
itemslist.forEach((items) => entireMenu.push(items));
return entireMenu;
})
// lastly we need to handle any errors from the promises
.catch((error) => { success: false });
//...
So now we have...
listEntireMenu(req, res) {
return MenuCategory.find({}, function(err, menuCategories) {
if (err) {
throw err
} else {
entireMenu = [];
return /* the promise all call from above will go right here */;
}
}
I hope it works out, thanks...

Knejx Js only promises

how to make this function work. Only a promise comes back to me.
codeProducts.forEach((code, index) => {
const qt = app.db('products').where('code',code).first().then(result => result.quantity)
data[index] = {
code: code,
quantity: qt
}
})
return data
There is two or three ways that you can solve this-
Approach: 1
I will make just one call to database.(I love my database :D) as-
let codeProducts = [55, 68, 96];
knex('products')
.whereIn('code', codeProducts)
.then((results) => {
// response to api call
console.log(results);
});
Approach: 2 (I don't like this approach. Too many call on db)
async function getData(codes) {
try {
let results = [];
for (let i = 0; i < codes.length; i++) {
let dbQuery = await knex('products').where('code', codes[i]).first();
results.push(dbQuery);
}
return results;
} catch (e) {
console.log(e);
}
}
const codeProducts = [54, 95];
getData()
.then((res) => {
console.log(res);
})
let data = codeProducts.map((code, index) => {
return app.db('products').where('code',code).first()
.then(result => {
return {
code: code,
quantity: result.quantity
}
})
})
return data
This code should fix your problem. You are accessing the quantity outside of the promise. In order to set the quantity on the data array, you need to do it inside the then

How to wait for an asynchronous process to complete inside a for loop before incrementing the loop

I need to iterate through an array. With each iteration, I need to update my database. I need to wait for the first update to be complete and then make the second update.
After searching through several answers, I found ASYNC/AWAIT feature of ES2017. However, I have not been able to implement it so far. The updates are happening randomly and not in a sequence. Please let me know how to implement ASYNC/AWAIT in this situation
Here is my code snippet:
function findRecipe(product, qty) {
return new Promise((resolve, reject) => {
Recipe.findOne({
product: product
}, (err, recipe) => {
if (err) {
reject(err)
} else {
for (let i = 0; i < recipe.items.length; i++) {
Item.findOne({
name: recipe.items[i].name
}, (err, item) => {
if (err) {
reject(err)
} else {
var lessAmt = recipe.quantities[i] * qty;
item.stock -= lessAmt;
item.save((err, item) => {
if (err) {
console.log(err)
} else {
resolve(item)
}
})
}
})
}
}
})
});
}
for (let i = 0; i < bill.product.length; i++) {
//Calling function for updates for each item
findRecipe(bill.product[i], bill.qty[i])
}
It looks like you are nearly there, Just Wrap the loop in a function and make it async.
async function updateAllRecipe(){
for(let i=0;i<bill.product.length;i++){
//Calling function for updates for each item
await findRecipe(bill.product[i],bill.qty[i])
}
}
But seriously though, I think you can leverage of parallelism here using the Promise.All. Is it really necessary to wait for the recipe to finish before queing the next findRecipe method? If not use the promise.all for it to perform faster
Async Await is easy to implement once you know the basic concept of asynchronous nature of Nodejs, I have used for...of loop here which also works asynchronously.
//Async function to perform database updates
async function findRecipe(product, qty) {
try {
let recipe = await Recipe.findOne({ product: product });
let i = 0;
for (itemObj of recipe.items) {
let item = await Item.findOne({ name: itemObj.name });
var lessAmt = recipe.quantities[i] * qty;
item.stock -= lessAmt;
let updatedItem = await item.save();
i++;
}
return true;
}
catch (err) {
return err;
}
}
async function someasyncFunction() {
for (let i = 0; i < bill.product.length; i++) {
//Calling function for updates for each item
await findRecipe(bill.product[i], bill.qty[i])
}
}
Use Promise.all and start process parallel. It'll increase the performance of API.
async function findRecipe(product, qty) {
try {
let recipe = await Recipe.findOne({
product: product
});
const items = await Promise.all(recipe.items.map(itemObj => Item.findOne({
name: itemObj.name
})));
const items = await Promise.all(items.map((item, i) => {
var lessAmt = recipe.quantities[i] * qty;
item.stock -= lessAmt;
return item.save();
}));
return true;
} catch (err) {
return err;
}
}
async function someasyncFunction() {
await Prmise.all(bill.product.map((product, i) => findRecipe(product, bill.qty[i])));
}

Resources