I'm wrestling with nested promise loops and having trouble finding a working solution.
I looked around and found this: https://stackoverflow.com/a/29396005/3560729
The promiseWhile function seems to have what I need but I'm having trouble getting the nesting to return to the outer loop
promiseWhile:
function promiseWhile(predicate, action) {
function loop() {
if (!predicate()) return;
return Promise.resolve(action()).then(loop);
}
return Promise.resolve().then(loop);
}
Nested Loop:
let outerArray = outerArrayOfObjects;
let returnArray = [];
let returnArrayIndex = 0;
let outerIndex = 0;
let outerLength = outerArray.length;
let passObject = { };
promiseWhile(function() {
return outerIndex < outerLength;
}, function() {
let innerIndex = 0;
let innerLength = outerArray[outerIndex].innerArray.length;
passObject = {
innerObject: outerArray[outerIndex].innerArray[innerIndex],
returnArray: returnArray,
returnArrayIndex: returnArrayIndex
};
promiseWhile(function() {
return innerIndex < innerLength;
}, function() {
return new Promise(function(resolve, reject){
Promise.all([
promiseFunction1(innerObject),
promiseFunction2(innerObject),
promiseFunction3(innerObject),
])
.then(function (allMappings) {
passObject.returnArray[returnArrayIndex++] = {
"result1": allMappings[0],
"result2": allMappings[1],
"result3": allMappings[2]
}
offersIndex++;
return resolve(passObject)
})
.catch(function (err) {
offersIndex++;
return reject(err);
})
})
})
outerIndex++;
}).then(function() {
return resolve(passObject);
});
})
}
I think my main questions are: Where do I process the results? How should I pass the values such that the return array is built properly?
The promiseWhile above is good for performing actions but not good for setting values in a nested loop and returning the results.
I ended up going with an approach using bluebird:
var Promise = require('bluebird');
let outerArray = object.outerArray;
let returnArray = [];
let returnIndex = 0;
Promise.map(outerArray, function (outerArrayObject) {
let innerArray = outerArrayObject.innerArray;
let outerArrayValue = outerArrayObject.value;
Promise.map(innerArray, function (innerArrayObject) {
Promise.all([
PromiseFunction1(innerArrayObject),
PromiseFunction2(innerArrayObject),
PromiseFunction3(innerArrayObject),
])
.then(function (allResults) {
returnArray[returnIndex++] = {
"result1": allResults[0],
"result2": allResults[1],
"result3": allResults[2],
"result4": outerArrayValue,
}
return resolve(returnArray);
})
.catch(function (err) {
return reject(err);
})
})
})
.then(function () {
return resolve(returnArray)
}
).catch(function(err){
return reject(err);
}
)
}
Related
please someone help, I just cant get it.
Can you please help on how to make async/await or promise (doneCb), so script waits for first vlc_snmp(..) to finish and then to call next?
Example:
function doneCb(error) {
console.log(final_result);
final_result = [];
if (error)
console.error(error.toString());
}
function feedCb(varbinds) {
for (var i = 0; i < varbinds.length; i++) {
if (snmp.isVarbindError(varbinds[i]))
console.error(snmp.varbindError(varbinds[i]));
else {
var snmp_rez = {
oid: (varbinds[i].oid).toString()
value: (varbinds[i].value).toString()
};
final_result.push(snmp_rez);
}
}
}
var session = snmp.createSession(VLC_IP, "public", options);
var maxRepetitions = 20;
function vlc_snmp(OID) {
session.subtree(OID_, maxRepetitions, feedCb, doneCb);
}
vlc_snmp(OID_SERIAL_NUMBER);
//wait OID_SERIAL_NUMBER to finish and then call next
vlc_snmp(OID_DEVICE_NAME);
You should be able to use the async / await statements to wait for your done callback to be called. We wrap this callback in the vlc_snmp function, and return a Promise. This allows us to use the await statement.
I've mocked out some of the code that I don't have access to, it should behave somewhat similarly to the real code.
The key point here is, when a function returns a Promise we can await the result in an async function, that will give you the behaviour you wish.
final_result = [];
const VLC_IP = "";
const options = {};
const OID_ = "OID_";
const OID_SERIAL_NUMBER = "OID_SERIAL_NUMBER"
const OID_DEVICE_NAME = "OID_DEVICE_NAME"
// Mock out snmp to call feedcb and donecb
const snmp = {
createSession(...args) {
return {
subtree(oid, maxRepetitions, feedCb, doneCb) {
setTimeout(feedCb, 500, [{ oid, value: 42}])
setTimeout(doneCb, 1000);
}
}
},
isVarbindError(input) {
return false;
}
}
function doneCb(error) {
console.log("doneCb: final_result:", final_result);
final_result = [];
if (error)
console.error("doneCb: Error:", error.toString());
}
function feedCb(varbinds) {
for (var i = 0; i < varbinds.length; i++) {
if (snmp.isVarbindError(varbinds[i]))
console.error(snmp.varbindError(varbinds[i]));
else {
var snmp_rez = {
oid: (varbinds[i].oid).toString(),
value: (varbinds[i].value).toString()
};
final_result.push(snmp_rez);
}
}
}
var session = snmp.createSession(VLC_IP, "public", options);
var maxRepetitions = 20;
function vlc_snmp(OID) {
return new Promise((resolve,reject) => {
session.subtree(OID, maxRepetitions, feedCb, (error) => {
// This is a wrapper callback on doneCb
// Always call the doneCb
doneCb(error);
if (error) {
reject(error);
} else {
resolve();
}
});
});
}
async function run_vls_snmp() {
console.log("run_vls_snmp: Calling vlc_snmp(OID_SERIAL_NUMBER)...");
await vlc_snmp(OID_SERIAL_NUMBER);
console.log("run_vls_snmp: Calling vlc_snmp(OID_DEVICE_NAME)...");
//wait OID_SERIAL_NUMBER to finish and then call next
await vlc_snmp(OID_DEVICE_NAME);
}
run_vls_snmp();
I need help with code below. I get an array of items from the client then the goal is to save them in mongodb and return the list classified as 'saved' and 'failed' items. sample of failed items are those that are duplicate on a unique attribute.
I know the code below will not work because of variable scope. how do i get around it? the code below returns an empty array for both savedItems and failedItems. Thanks!
router.post('/addItems', async (req, res, next) => {
let items = req.body;
let result = {
savedItems: [],
failedItems: []
};
function saveData() {
for (i = 0; i < items.length; i++) {
item = items[i];
Model.create({ ...item }, (err, data) => {
if (err) {
result.failedItems.push(item);
} else {
result.savedItems.push(item);
}
});
}
return result;
}
saveData().then(result => {
res.send({
results: result
});
});
});
router.post('/addItems', async (req, res, next) => {
// use try catch when use async
try {
let items = req.body;
let result = {
savedItems: [],
failedItems: []
};
for (let i = 0; i < items.length; i++) {
const item = items[i];
// use the returned promise instead of callback for Model.create
const data = await Model.create({ ...item });
result.savedItems.push(item);
// if also need to handle failed item in result use anathor try catch inside
/*try {
const data = await Model.create({ ...item });
result.savedItems.push(item);
} catch( err ) {
result.failedItems.push(item);
}*/
}
res.send({
results: result
});
} catch( err ) {
// To all the errors unexpected errors + thrown rejected promises
res.send({
error: err
});
}
});
Your saveData method didn't return a promise, try this
function saveData() {
return new Promise(resolve => {
let items = req.body;
let result = {
savedItems: [],
failedItems: []
};
let promises = [];
for (i = 0; i < items.length; i++) {
item = items[i];
let promise = new Promise(resolve => {
Model.create({ ...item }, (err, data) => {
if (err) {
result.failedItems.push(item);
} else {
result.savedItems.push(item);
}
resolve();
});
});
promises.push(promise);
}
Promise.all(promises).then(() => resolve(result));
})
}
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])));
}
I have some problems with the multiple promises in my code. There is no way to return to items who are not in the database. I changed the code multiple times but no luck. The only data it returns is "datas": [
null,
null
]
This is my code
var start = function(offset, entry) {
return new Promise(function(resolve, reject) {
rp('************' + entry).then(function(repos) {
resolve(repos);
}).catch(function(err) {
reject(err);
});
});
};
var findnewones = function(iten) {
return new Promise(function(resolve, reject) {
return Promise.all(iten.items.map(function(ndtrcitem) {
return new Promise(function(resolve, reject) {
Items.findOne({"metadata.trcid": ndtrcitem.metadata.trcid}).exec(function(err, doc) {
if (!doc) {
resolve(ndtrcitem);
}
});
})
})).then(datas => {
resolve(datas);
});
})
}
exports.find = function(req, res, next) {
var ndite = ["locations", "events"];
var items = [];
return Promise.all(ndite.map(function(entry) {
return start(0, entry).then(function(res) {
for (i = 0; i <= res.count; i += 10) {
return start(i, entry).then(function(iten) {
findnewones(iten).then(function(dat) {
items.push(dat);
});
});
}
return items;
})
})).then(datas => {
res.json({datas});
});
}
I think because the for loop there is synchronous and it's not waiting for the start() promise to resolve.
for (i = 0; i <= res.count; i += 10) {
return start(i, entry).then(function(iten) {
findnewones(iten).then(function(dat) {
items.push(dat);
});
});
}
I have replaced it with async/await, don't know if it will work right away, I am just providing you with a hint in this very complicated promise chain. If it or any variation of it works please update this answer.
exports.find = function (req, res, next) {
var ndite = ["locations", "events"];
var items = [];
return Promise.all(ndite.map(function (entry) {
return start(0, entry)
.then(async function (res) {////// this
for (i = 0; i <= res.count; i += 10) {
await start(i, entry).then(function (iten) { ////this
findnewones(iten).then(function (dat) {
items.push(dat);
});
});
}
return items;
})
})).then(datas => {
res.json({
datas
});
});
}
I am trying to load a hierarchy in my database. I have a column with parentId in my table so every row can have a parent. But I am having problems using recursion and promises.
function read (options) {
return serviceItemAttributeModel.findOne({
id: options.id,
id_organization: options.idOrganization
})
.then((attribute) => {
if (attribute) {
return loadChildren(attribute, attribute);
} else {
return attribute;
}
});
}
function loadChildren (root, attribute) {
return serviceItemAttributeModel.findAll({
where: {
id_parent: attribute.id
}
})
.then((attributes) => {
if (!attributes) {
return root;
} else {
attribute.serviceItemAttributes = [];
attributes.forEach(function (each) {
attribute.serviceItemAttributes.push(each);
return loadChildren(root, each);
});
}
});
}
So, I call read that calls loadChildren to recursively try to load all entities (by looking children of an entity) and I get an undefined value. Any ideas?
I am also getting an error on console: a promise was created in a handler but was not returned from it.
EDIT:
Came up if this solution after Nosyara help. thanks!:
function read (options) {
return serviceItemAttributeModel.findOne({
where: {
id: options.attributeId,
id_organization: options.idOrganization
}
})
.then((attribute) => {
if (!attribute) {
return new Promise(function (resolve, reject) {
resolve(attribute);
});
} else {
return new Promise(function (resolve, reject) {
attribute.queryCount = 1;
resolve(attribute);
})
.then((attribute) => loadChildren(attribute, attribute));
}
});
}
function loadChildren (root, attribute) {
return new Promise(function (resolve, reject) {
return serviceItemAttributeModel.findAll({
where: {
id_parent: attribute.id
}
})
.then((attributes) => {
attributes.length = attributes.length || 0;
root.queryCount = root.queryCount - 1 + attributes.length;
if (root.queryCount === 0) {
resolve(root);
} else if (root.queryCount > 10) {
let error = new Error('Service attribute hierarchy cant have more then 10 levels');
error.statusCode = 500;
reject(error);
} else {
attribute.serviceItemAttributes = [];
attributes.forEach(function (each) {
attribute.serviceItemAttributes.push(each);
return loadChildren(root, each).then(() => {
resolve(root);
});
});
}
});
});
}
You messing up with async calls and returns. You can convert both function to async, and pass through result structure to be updated. Example:
function read(...) {
return new Promise(function (accept, reject) {
// You code goes here, but instead of return
accept(resultFromAsyncFunction);
});
}
// ...
read(...).then(function(resultData) { ... });
Here is example of Promise recursion.