Loop with nested promise - node.js

What is the best way to get result from a promise inside a For loop.
In this example code resultArray is not complete when the loops ends.
var resultArr = [];
var itemArray1 = [1, 2, 3, 4, 5];
var itemArray2 = ['a','b','c','d','e'];
for (var a = 0; a < itemArray1.length; a++) {
for (var b = 0; b < itemArray2.length; b++) {
myPromise(a,b)
.then(function(result) {
if (result != null) {
resultArr.push(result);
}
});
}
}
// resultArray is still not complete
function myPromise(a,b) {
return new Promise(function(resolve, reject) {
// request to mongodb
myTable.findOne({ _id:a, name:b }, function(err,result) {
if (err) throw err;
resolve(result);
});
});
}

In my opinion, the cleanest way to do things like this is to use Promise.all() with Array#map. Also, make sure to give keep your functions clean and concise, and give them meaningful names!
var itemArray1 = [1, 2, 3, 4, 5];
var itemArray2 = ['a','b','c','d','e'];
function flatten(arrays) {
return [].concat(arrays);
}
function queryAs() {
return Promise.all(itemArray1.map(queryBs))
// the result is an array of arrays, so we'll flatten them here
.then(flatten);
}
function queryBs(a) {
return Promise.all(itemArray2.map(function (b) {
return performQuery(a, b);
}));
}
// resultArray is still not complete
function performQuery(a, b) {
return new Promise(function(resolve, reject) {
// request to mongodb
myTable.findOne({ _id:a, name:b }, function(err,result) {
if (err) throw err;
resolve(result);
});
});
}
queryAs().then(function (results) {
console.log(results);
});

You could use a combination of forEach and Promise.all
var resultArr = [];
var itemArray1 = [1, 2, 3, 4, 5];
var itemArray2 = ['a', 'b', 'c', 'd', 'e'];
var myPromise = (item1, item2) => new Promise((resolve, reject) => {
myTable.findOne({
_id: item1,
name: item2
}, function(err, result) {
if (err) reject(err);
else resolve(result);
});
});
var promises = [];
itemArray1.forEach(item1 => {
itemArray2.forEach(item2 => {
promises.push(myPromise(item1, item2));
});
});
Promise.all(promises).then(result => {
console.log(result);
}).catch(err => {
console.error(err.message);
});

Related

Modify the value of a variable outside callback with the callback inside loop

I am new to Nodejs and I am facing with a problem: Modify the value of a variable outside callback with the callback inside a loop.
I am coding online-judgle project, this is my function to check output of a program with answer from database. I created result object to store amount of correct testcase.
function compareResult(fileName, problem, timeLimit, callback) {
const cp = require('child_process');
const exePath = 'submit\\' + fileName + '.exe';
const child = cp.spawn(exePath, ['--from=markdown', '--to=html'], {timeout: timeLimit});
MongoClient.connect(uri, function(err, db) {
if (err) throw err;
var dbo = db.db(dbName);
var query = { id_problem: problem, is_eg: "false" };
var proj = { projection: {input: 1, output: 1} };
dbo.collection("sample").find(query, proj).toArray(function(err, arr) {
if (err) throw err;
if (arr != null) {
var result = {
correct: 0,
total: arr.length
};
for (const json of arr) {
const answer = json['output'];
child.stdin.write(json['input']);
child.stdout.on('data', function(data) {
if (data == answer) {
result.correct += 1; // I want to modify result object here.
}
});
child.stdin.end();
};
console.log(result);
callback(result);
}
});
});
I want to modify result object in that place. How will I do it?
function compareResult(fileName, problem, timeLimit, callback) {
const cp = require('child_process');
const exePath = 'submit\\' + fileName + '.exe';
const child = cp.spawn(exePath, ['--from=markdown', '--to=html'], {timeout: timeLimit});
MongoClient.connect(uri, function(err, db) {
if (err) throw err;
var dbo = db.db(dbName);
var query = { id_problem: problem, is_eg: "false" };
var proj = { projection: {input: 1, output: 1} };
dbo.collection("sample").find(query, proj).toArray(function(err, arr) {
if (err) throw err;
if (arr != null) {
var result = {
correct: 0,
total: arr.length
};
for (const json of arr) {
const answer = json['output'];
child.stdin.write(json['input']);
child.stdout.on('data', function(data) {
if (data == answer) {
result.correct += 1;
}
// Decrement total here to track how many 'data' events have been emitted
result.total--;
if (result.total === 0) {
// All 'data' events have been emitted, so call the callback function
callback(result);
}
});
child.stdin.end();
};
}
});
});
}

How to return 2 arrays after saving data to mongodb using node js

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));
})
}

multiple promises in api server node returns null

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
});
});
}

Promise looping over nested arrays

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);
}
)
}

Q Promise Nodejs how to resolve in loop

i have code written in nodejs make me confusying using Q Promises
theFunction()
.then(function(data) {
var deferred = Q.defer()
var result = [];
for(i=0; i < data.length; i++) {
secondFunc(data.item)
.then(function(data2) {
data.more = data2.item
});
result.push(data);
}
deferred.resolve(result);
deferred.promise();
});
i want data in second function inside loop can push into result
so my previous data is like this
[
{
id: 1,
item: 1,
hero: 2
},
{
id: 1,
item: 1,
hero: 2
}
]
and so like this
[
{
id: 1,
item: 1,
hero: 2,
more: {
list: 1
}
},
{
id: 1,
item: 1,
hero: 2,
more: {
list: 4
}
}
]
I've tried several ways start by entering the command
deferred.resolve (); statement in the loop and only showing 1 data
have any solution ?
Instead of a deferred.resolve() on an array which will resolve immediately, use Q.all which waits for an array of promises:
theFunction()
.then(function(data) {
var result = [];
for(var i=0; i < data.length; i++) (function(i){
result.push(secondFunc(data[i].item)
.then(function(data2) {
data[i].more = data2.item;
return data[i];
}));
})(i); // avoid the closure loop problem
return Q.all(result)
});
Or even better:
theFunction()
.then(function(data) {
return Q.all(data.map(function(item)
return secondFunc(item)
.then(function(data2) {
item.more = data2.item;
return item;
});
});
});
I know this is a older post but I've the same problem and did not found any solution. Maybe someone here find a good solution very fast.
function CompareTeamspeakClients(forumUsers) {
var promises = [];
var tsClient = new TeamSpeakClient("127.0.0.1", 10011);
tsClient.send("login", {
client_login_name: "serveradmin",
client_login_password: "M+h8YzUA"
}, function(err, response){
if (err) deferred.reject(err);
});
tsClient.send("use", {
port: 9987
}, function(err, response){
if (err) deferred.reject(err);
});
forumUsers.forEach(function(user, index){
var deferred = Q.defer();
tsClient.send("clientdbfind", ["uid"], {
pattern: user.tsid
}, function(err, response){
if (err) deferred.reject(err);
if (response) {
tsClient.send("clientdbinfo", {
cldbid: response.cldbid
}, function(err, response){
if (err) deferred.reject(err);
forumUsers[index]['tsdbid'] = response.client_database_id;
forumUsers[index]['tsnickname'] = response.client_nickname;
forumUsers[index]['tslastconnected'] = response.client_lastconnected;
deferred.resolve(forumUsers);
});
}
});
promises.push(deferred.promise);
});
console.log(promises);
return Q.all(promises);
}

Resources