Node JS losing for loop index variable [duplicate] - javascript

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
When I use a simple for loop to access array values, the index variable gets lost therefore unable to access array. Using index number instead of variable works but not variable. This is the most annoying code ever.
/* jshint esnext: true, asi: true */
var neo4j = require('node-neo4j')
// Create the neo4J object
var db = new neo4j(serverURI)
exports.addPerson = (body, callback) => {
if (body.skills) {
var sentSkills = body.skills
var arraySkills = sentSkills.split(',')
}else {
var sentSkills = []
}
const sentName = body.name
const sentEmail = body.email
const sentUsername = body.username
const sentPassword = body.password
const lecturerStatus = body.lecturer
db.readNodesWithLabelsAndProperties('Person',{ email: sentEmail }, function (err, node) {
if (err) {
return console.log(err)
}
if (node.length > 0){
// The user already exists
callback({code:401,status:'failed',message:'Person already exsits with the name '+sentName,data:sentName})
}
else {
// Insert new Person
db.insertNode({
name: sentName,
email: sentEmail,
skills: sentSkills,
username: sentUsername,
password: sentPassword,
lecturer: lecturerStatus
}, 'Person', function (err, node) {
personNode = node
if (err) {
return console.log(err+1)
}
else {
// I hate you for not working
// The i = 0 variable is not accessible -> arraySkill[i]^.trim()
// ERROR: cannot read property trim of undefined
console.log("success")
for (i = 0; i < arraySkills.length; i++){
arraySkills = body.skills.split(',')
db.cypherQuery("MATCH (s:Skill {name:'"+arraySkills[i].trim()+"'}) RETURN s", function(err, node){
if (err){
console.log("ERROR1")
console.log(err)
}
else {
console.log(arraySkills[0])
if (node.data == '')
{
db.cypherQuery("CREATE (s:Skill {name:'"+arraySkills[i].trim()+"'}) RETURN s", function(err, node){
if (err){
console.log("ERROR2")
console.log(err)
}
else {
console.log(node)
db.cypherQuery("MATCH (p:Person), (s:Skill) WHERE p.name = '"+sentName.trim()+"' AND s.name = '"+arraySkills[i].trim()+"' CREATE (p)-[r:knows]->(s) RETURN r", function(err, node){
if (err){
console.log("ERROR3")
console.log(err)
}
else {
console.log(node)
console.log("Success")
}
})
}
})
}
}
})
};
}
})
// Output node data.
callback({code:201,status:'success',message:'Person Added In '+sentName+' found...',data:node})
}
})
}

That's a closure problem, to fix it you have to move your $http call to a new function like this.
for (i = 0; i < arraySkills.length; i++){
var skills = body.skills.split(',');
dbQuery(skills,i); // In this function you have to write all the stuff you got under arraySkills = body.skills.split(',')
}

Related

Node.js and SQL: How can I get the value outside of the for loop?

Using Node.js and a MySQL database I'm working on a small project.
I'm trying to loop through an array to get some values out of a MySQL database. I'm searching for the corresponding "medicine ID" to an "medicine name" that the user entered. My Code is working correctly and this is how it looks.
var medizinArray = [];
function appendArray(input) {
medizinArray.push(input);
}
var sqlMedNameToId = "SELECT MedikamentId FROM Medikament WHERE Bezeichnung = ?"
for (var i=0;i<medicineMontag.length;i++){
var montagsMedizin = medicineMontag[i];
mySqlConnection.query(sqlMedNameToId, montagsMedizin, function(err, rows, fields){
if(!err) {
result = rows[0].MedikamentId;
appendArray(result);
} else {
console.log(err);
}
})
}
console.log(medizinArray);
The code is working but I can't get the medizinArray out of the for loop. In my console I get an empty array. When I put the console.log(medizinArray) inside the for loop I get the array that I want.
I'm currently not familiar with Promises. I read about it and saw some other questions but I can't figure out how to implement Promises in my code.
SQL operations are asynchronous, so to obtain the result outside of the callback you need to wrap them in a Promise and call the resolve() function when the operation is successful. Use any of the techniques below:
Async/await technique:
(async function(){
let medizinArray = [];
function appendArray(input) {
medizinArray.push(input);
}
let sqlMedNameToId = "SELECT MedikamentId FROM Medikament WHERE Bezeichnung = ?"
await new Promise(function(resolve, reject){
let e;
for (let i=0;i<medicineMontag.length;i++){
let montagsMedizin = medicineMontag[i];
mySqlConnection.query(sqlMedNameToId, montagsMedizin, function(err, rows, fields){
if(e) return;
if(!err) {
result = rows[0].MedikamentId;
appendArray(result);
} else {
//console.log(err);
e = true;
return reject(err);
}
if(i == medicineMontag.length-1) resolve(result);
})
}
}
);
console.log(medizinArray);//now medizinArray shows up here
})().catch(function(err){console.log(err)});
Promise/then technique:
let medizinArray = [];
function appendArray(input) {
medizinArray.push(input);
}
let sqlMedNameToId = "SELECT MedikamentId FROM Medikament WHERE Bezeichnung = ?"
new Promise(function(resolve, reject){
let e;
for (let i=0;i<medicineMontag.length;i++){
let montagsMedizin = medicineMontag[i];
mySqlConnection.query(sqlMedNameToId, montagsMedizin, function(err, rows, fields){
if(e) return;
if(!err) {
result = rows[0].MedikamentId;
appendArray(result);
} else {
console.log(err);
e = true;
return reject(err);
}
if(i == medicineMontag.length-1) resolve(result);
})
}
}
).then(function(result){
console.log(medizinArray);//now medizinArray shows up here
}).catch(function(err){
console.log(err);
});

forEach loop behaving strangely with called function values logged at end of the loop instead of during

EDIT: currently i think the problem with this is that forEach is not promise aware. https://zellwk.com/blog/async-await-in-loops/
I am trying to apply a node javascript translation function (ive put it at the end of the post because it is quite long) to loop over an array of values. However when i loop for some reason i'm only getting certain parts of my looped function to appear after the loop has completed: Allow me to make this more clear:
array = [["hello", "hello" ],
["my", "my", ],
["name", "name" ],
["is", "my" ],
["joe", "joe"]]
function process (item,index){
const translate = require('#vitalets/google-translate-api');
console.log('loopaction'); //this shows that the loop is executing
translate(item[0], {to: 'sp'}).then(result => {
console.log(result.text);
}).catch(err => {
console.error(err);
})
array.forEach(process); // Applying the function process to the array in a ForEach loop
from this i am getting
loopaction
loopaction
loopaction
loopaction
loopaction
hola
mi
nombre
es
joe
So it seems that the forEach loop is completing before the values are being allowed to be displayed. Which is something i really don't understand since the array values are being translated correctly and then logged out in the correct order. As if they had been stored in the memory for later. And then called at the end of the forEach loop order.
The translate function looks like this:
function translate(text, opts, gotopts) {
opts = opts || {};
gotopts = gotopts || {};
var e;
[opts.from, opts.to].forEach(function (lang) {
if (lang && !languages.isSupported(lang)) {
e = new Error();
e.code = 400;
e.message = 'The language \'' + lang + '\' is not supported';
}
});
if (e) {
return new Promise(function (resolve, reject) {
reject(e);
});
}
opts.from = opts.from || 'auto';
opts.to = opts.to || 'en';
opts.tld = opts.tld || 'com';
opts.from = languages.getCode(opts.from);
opts.to = languages.getCode(opts.to);
var url = 'https://translate.google.' + opts.tld;
return got(url, gotopts).then(function (res) {
var data = {
'rpcids': 'MkEWBc',
'f.sid': extract('FdrFJe', res),
'bl': extract('cfb2h', res),
'hl': 'en-US',
'soc-app': 1,
'soc-platform': 1,
'soc-device': 1,
'_reqid': Math.floor(1000 + (Math.random() * 9000)),
'rt': 'c'
};
return data;
}).then(function (data) {
url = url + '/_/TranslateWebserverUi/data/batchexecute?' + querystring.stringify(data);
gotopts.body = 'f.req=' + encodeURIComponent(JSON.stringify([[['MkEWBc', JSON.stringify([[text, opts.from, opts.to, true], [null]]), null, 'generic']]])) + '&';
gotopts.headers['content-type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
return got.post(url, gotopts).then(function (res) {
var json = res.body.slice(6);
var length = '';
var result = {
text: '',
pronunciation: '',
from: {
language: {
didYouMean: false,
iso: ''
},
text: {
autoCorrected: false,
value: '',
didYouMean: false
}
},
raw: ''
};
try {
length = /^\d+/.exec(json)[0];
json = JSON.parse(json.slice(length.length, parseInt(length, 10) + length.length));
json = JSON.parse(json[0][2]);
result.raw = json;
} catch (e) {
return result;
}
if (json[1][0][0][5] === undefined) {
// translation not found, could be a hyperlink?
result.text = json[1][0][0][0];
} else {
json[1][0][0][5].forEach(function (obj) {
if (obj[0]) {
result.text += obj[0];
}
});
}
result.pronunciation = json[1][0][0][1];
// From language
if (json[0] && json[0][1] && json[0][1][1]) {
result.from.language.didYouMean = true;
result.from.language.iso = json[0][1][1][0];
} else if (json[1][3] === 'auto') {
result.from.language.iso = json[2];
} else {
result.from.language.iso = json[1][3];
}
// Did you mean & autocorrect
if (json[0] && json[0][1] && json[0][1][0]) {
var str = json[0][1][0][0][1];
str = str.replace(/<b>(<i>)?/g, '[');
str = str.replace(/(<\/i>)?<\/b>/g, ']');
result.from.text.value = str;
if (json[0][1][0][2] === 1) {
result.from.text.autoCorrected = true;
} else {
result.from.text.didYouMean = true;
}
}
return result;
}).catch(function (err) {
err.message += `\nUrl: ${url}`;
if (err.statusCode !== undefined && err.statusCode !== 200) {
err.code = 'BAD_REQUEST';
} else {
err.code = 'BAD_NETWORK';
}
throw err;
});
});
}
I realise there is a promise format and the problem im having may have to do with the asychronisity of the function and how the long the promise is taking to get resolved. I cant seem to figure out why the promise is not resolving or displaying after my forEach function is completely looped yet it seems to be saved correctly and in the correct order. Very odd.
Any ideas about what is it about the function translate() that is making this happen? Is there anyway i can rewrite my function process () to make sure that the translate functions resolved promise and the .then() in function process () is fully executed before moving on?
You are correct, you are using promises, so translate() will run asynchronously (in the background) while the rest of your code is executing. That is why you go through all the foreach() before the translate function returns, and therefore you get that output.
However, there is also a problem using a forEach loop in an async function or a promise block. The callback function is not being awaited. Therefore, the promise chain is broken, resulting in the unexpected behavior.
Don't use forEach loop in a promise or async function. Instead, use a for loop to iterate through the items of the array:
To avoid these problems, change the forEach loop to a For loop and use async and await like this:
async function process (item,index){
const translate = require('#vitalets/google-translate-api');
console.log('loopaction'); //this shows that the loop is executing
await translate(item[0], {to: 'sp'})
.then(result => {
console.log(result.text);
})
.catch(err => {
console.error(err);
})
}
async function main() {
array = [["hello", "hello" ],
["my", "my" ],
["name", "name" ],
["is", "my" ],
["joe", "joe"]]
for (let i = 0; i < array.length; i++) {
await process(array[i], i);
}
}
main()
await makes the function wait until the promise is resolved.
NOTE: You tried to create a timeout with object.sleep(), this doesn't exist in javascript, use setTimeout() instead, refer to: Sleep() in Javascript

How to stop a js promise.all() on a condition?

I have this piece of code
let promiseList = []
for (let i in data) {
let promise = checkIfMember(data[i].tg_id, chatId).then(res => {
if (res) {
//if the user has a undefined username it won't be rained
if (data[i].username != "undefined") {
console.log(data[i].username)
members.push([data[i].tg_id, data[i].username])
}
}
}).catch(err => {
console.log(err)
})
promiseList.push(promise)
}
return Promise.all(promiseList).then((res) => {
//list of members that are in the room , we randomize it before returning
shuffleArray(members)
if(numberOfUsers > members.length){
return[false, members.length]
}else{
members = members.slice(0,numberOfUsers)
return [true, members]
}
});
Basically I have a promise list that is being filled. And then all of them are executed with a promiseAll. The problem is that I dont want all of them to execute, I want to to do something like this:
let promiseList = []
let validmembers = 0;
for (let i in data) {
let promise = checkIfMember(data[i].tg_id, chatId).then(res => {
if (res) {
//if the user has a undefined username it won't be rained
if (data[i].username != "undefined") {
console.log(data[i].username)
members.push([data[i].tg_id, data[i].username])
//stop there the execution
validmembers++;
if(validmembers == numberOfUsers){
return;
}
}
}
}).catch(err => {
console.log(err)
})
promiseList.push(promise)
}
return Promise.all(promiseList).then((res) => {
//list of members that are in the room , we randomize it before returning
shuffleArray(members)
if(numberOfUsers > members.length){
return[false, members.length]
}else{
members = members.slice(0,numberOfUsers)
return [true, members]
}
});
But the problem is that the promises are async, so the promiselist filling doesnt stop.
How would I solve this?
It appears you want the members array to have no more than numberOfUsers entries in it due to the promises. Since the promises run asynchronously you can't stop the checkIfMember function from being called, but inside of its then function you could avoid accumulating more members like this:
// don't use `!== "undefined"` unless it can actually be the string "undefined"
if (data[i].username) {
if (members.length < numberOfUsers) {
console.log(data[i].username);
members.push([data[i].tg_id, data[i].username]);
}
}
By only pushing to members when it's not yet full you will avoid adding more than numberOfUsers and don't need to do the slice later.
If instead you want to avoid the entire promise work when enough have been collected, then you can serialize them like this:
async function yourFunction() {
for (let i in data) {
if (members.length < numberOfUsers) {
// don't continue to the next 'i' until this one is done
await checkIfMember(data[i].tg_id, chatId)
.then(res => {
if (res) {
//if the user has a undefined username it won't be rained
if (data[i].username != "undefined") {
console.log(data[i].username);
members.push([data[i].tg_id, data[i].username]);
}
}
})
.catch(err => {
console.log(err);
});
} else {
break; // no need to continue once members is full
}
}
return [true, members];
}

Issue with mongoose.save never returning inside of promise

Update !!
I fixed my initial issue with the help of Dacre Denny answer below however when writing tests for my code it turned out that the changes were not being saved before the server responded therefor the company collection in my test database was empty, I fixed this issue with the following code
Companies.find({ company_name: company.company_name }).then(found => {
if (found.length !== 0) {
return res.status(400).json({ error: "Company already exists" });
}
var userForms = company.users;
company.users = [];
const finalCompany = new Companies(company);
console.log(finalCompany);
var userPromises = [];
for (var x = 0; x < userForms.length; x++) {
var user = userForms[x].user;
user.company = finalCompany._id;
userPromises.push(userCreation(user));
}
return Promise.all(userPromises).then(responses => {
for (var x in responses) {
if (!responses[x].errors) {
finalCompany.addUser(responses[x]._id);
} else {
res.status(400).json(responses[x]);
}
}
return finalCompany;
});
})
// I moved the save in here !!!
.then((finalCompany) => {
finalCompany.save().then(()=>{
res.status(200).json({signup:"Successful"});
})
},(err) => {
res.json({error: err});
});
});
Original Issue
I am trying to create a mongoose document to represent a company, this code saves the model in my db however it does not seem to be responding with a status code or reply to postman when I make a request
I've used a debugger to step through the code but I am very rusty on my JS and I am afraid I've gone into deep water with promises thats gone over my head.
router.post('/c_signup', auth.optional, (req, res, next) => {
const { body: { company } } = req;
var error_json = cbc(company);
if( error_json.errors.length > 0 ){
return res.status(422).json(error_json);
}
Companies.find({company_name: company.company_name})
.then((found) => {
if (found.length !== 0) {
return res.status(400).json({error: "Company already exists"});
}
var userForms = company.users;
company.users = [];
const finalCompany = new Companies(company);
var userPromises = [];
for (var x =0; x < userForms.length; x ++) {
var user = userForms[x].user;
user.company = finalCompany._id;
userPromises.push(userCreation(user));
}
Promise.all(userPromises).then((responses) => {
for (var x in responses){
if (!responses[x].errors){
finalCompany.addUser(responses[x]._id);
}
else {
res.status(400).json(responses[x]);
}
}
console.log("h2");
finalCompany.save(function () {
console.log("h3");
return res.status(200);
});
})
});
return res.status(404);
});
This is the output from the debug but the execution is hanging here
h2
h3
There are a few issues here:
First, the save() function is asynchronous. You'll need to account for that by ensuring the promise that save() returns, is returned to the handler that it's is called in.
The same is true with the call to Promise.all() - you'll need to add that promise to the respective promise chain by returning that promise to the enclosing handler (see notes below).
Also, make sure the request handler returns a response either via res.json(), res.send(), etc, or by simply calling res.end(). That signals that the request has completed and should address the "hanging behaviour".
Although your code includes res.json(), there are many cases where it's not guaranteed to be called. In such cases, the hanging behaviour would result. One way to address this would be to add res.end() to the end of your promise chain as shown below:
Companies.find({ company_name: company.company_name }).then(found => {
if (found.length !== 0) {
return res.status(400).json({ error: "Company already exists" });
}
var userForms = company.users;
company.users = [];
const finalCompany = new Companies(company);
var userPromises = [];
for (var x = 0; x < userForms.length; x++) {
var user = userForms[x].user;
user.company = finalCompany._id;
userPromises.push(userCreation(user));
}
/* Add return, ensure that the enclosing then() only resolves
after "all promises" here have completed */
return Promise.all(userPromises).then(responses => {
for (var x in responses) {
if (!responses[x].errors) {
finalCompany.addUser(responses[x]._id);
} else {
res.status(400).json(responses[x]);
}
}
console.log("h2");
/* Add return, ensure that the enclosing then() only resolves
after the asnyc "save" has completed */
return finalCompany.save(function() {
console.log("h3");
return res.status(200);
});
});
})
.then(() => {
res.end();
},(err) => {
console.error("Error:",err);
res.end();
});

Accessing an array inside REST call [duplicate]

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 6 years ago.
I have the below code where I have declared the array var filteredArray = [];. I make use filteredArray to push data in the callback function of a Rest call(This happens inside a for loop).
Once the for loop ends. I want to send the filteredArray to my angular controller. Here is my code. When I put console.log(filteredArray) outside the for loop, I get an empty array. Can someone please help?
assignedToMe: function(req, res) {
CustomerUser.find({
userEmail: {
$in: req.body.userEmail
}
}, function(err, docs) {
var filteredArray = [];
for (var i = 0; i < docs.length; i++) {
var url = config.pythiaApi + config.pythiaApiPrefix + '/quotes/' + docs[i]['quoteId'];
if (req.originalUrl.lastIndexOf('?') !== -1) {
url += req.originalUrl.substr(req.originalUrl.lastIndexOf('?'));
}
restClient.get(url, function(body, response) {
filteredArray.push(body.data)
})
}
console.log(filteredArray) // Gives empty array
res.status(200).json({});
});
}
Use asyncronous loop for async calls. For example async.times
assignedToMe: function(req, res) {
CustomerUser.find({
userEmail: {$in: req.body.userEmail}
}, function(err, docs) {
var filteredArray = [];
async.times(docs.length, function(i, next){
if(err) return next(err);
var url = config.pythiaApi + config.pythiaApiPrefix + '/quotes/'+docs[i]['quoteId'];
if (req.originalUrl.lastIndexOf('?') !== -1) {
url += req.originalUrl.substr(req.originalUrl.lastIndexOf('?'));
}
restClient.get(url, function (body, response) {
next(null, body.data)
})
}, function(err, result){
// result contains your array
res.status(200).json({});
})
});
}

Categories

Resources