Mutable variable is accessible from closure with promise and loop - javascript

I use the following code to generate a unique token for a user. Data for users is stored on MongoDB so I use promise to handle asynchronous talking to the db. In WebStorm I receive this warning : Mutable variable is accessible from closure with promise and loop. and I know there have been posts on SO about this thing, but I my case is more complicated. I know I may not even need to worry about it as I only use the last value of token but what I want to solve this issue in a correct way ?
var generateToken = function(userId) {
User.findOne({userId: userId}, function(err, user) {
if (user !== null) {
var loop = true;
while (loop) {
var token = Common.randomGenerator(20);
User.find({tokens: token}, function(err, result) {
if (err) {
loop = false;
return Promise.reject('Error querying the database');
} else {
if (result.length === 0) {
if (user.tokens === undefined){
user.tokens.push(token);
}
loop = false;
return Promise.resolve(token);
}
}
});
}
} else {
return Promise.reject('UserNotFound');
}
});
};
I came up with the following solution , is it correct ?
var generateToken = function(userId) {
User.findOne({userId: userId}, function(err, user) {
if (user !== null) {
var loop = true;
while (loop) {
var token = Common.randomGenerator(20);
(function(e){
User.find({tokens: e}, function(err, result) {
if (err) {
return Promise.reject('Error querying the database');
} else {
if (result.length === 0) {
if (user.tokens === undefined){
user.tokens = [];
}
user.tokens.push(e)
return Promise.resolve(e);
}
}
});
})(token);
}
} else {
return Promise.reject('UserNotFound');
}
});
};
NOTE As #Alex Nikulin suggested , I flipped loop to false before sending back the result of the promise. But still it's an infinite loop as it doesn't go into the User.find({tokens: e})....

Your problem is that, return statement is within function, not within loop.Your loop is infinite. I has decomposited your code.Or simply make loop false, when you resolve/reject promise. Also my code will wait each answer from User, and error will gone. And your variant with wrap function is correct (function(e){})(token).
var generateToken = function(userId) {
return new Promise(function(resolve, reject) {
User.findOne({userId: userId}, function(err, user) {
if (user !== null) {
userTokenIterator(user,resolve, reject);
} else {
reject('UserNotFound');
}
});
});
};
var addTokenToUser = function(token,user){
return new Promise(function(resolve, reject) {
User.find({tokens: token}, function(err, result) {
if (err) {
reject('Error querying the database');
} else {
var result = result.length === 0;
if (result) {
if(!user.tokens) {
user.tokens = []
}
user.tokens.push(token);
}
resolve(result);
}
});
});
};
var userTokenIterator = function (user, resolve, reject){
var token = Common.randomGenerator(20);
addTokenToUser(token, user).then(function(result){
if(result) {
resolve(token);
}else{
userTokenIterator(user,resolve, reject)
}
},function(error){
reject(error);
});
};

Related

how can i get value out of a child function [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 2 years ago.
var checkAcount = (usr, pass) => {
var sql = "SELECT * FROM account WHERE userName = '" +usr+"'" ;
con.query(sql, (err, result) => {
if(err) throw err;
if(result.length > 0){
bcrypt.compare(pass, result[0].password, function(err, result1) {
if(result1 == true){
return true;
}
else{
return false;
}
});
}
else {
return false;
}
});
return ???;
}
I have code like this and I don't know how to make this function return the value of the compare function (true or false). Add return in child function like I did seem not to work. Can somebody help me please.
You could return a promise:
async function checkAcount(usr, pass) => {
const sql = "SELECT * FROM account WHERE userName = ?" ;
return new Promise((resolve, reject) => {
con.query(sql, [usr], (err, result) => {
if(err) {
reject(err);
throw err;
}
if(result.length > 0){
bcrypt.compare(pass, result[0].password, function(err, result1) {
if(result1 == true){
resolve(result);
return true;
} else{
reject(err);
return false;
}
});
} else {
reject(err);
return false;
}
});
});
}
Don't build your SQL query with string concatenation. That allows SQL injection.
bcrypt returns a promise
https://www.npmjs.com/package/bcrypt#with-promises
bcrypt.compare(pass, result[0].password).then((result) => {
return result;
})

Array is blank after functions ran? [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 2 years ago.
app.post('/api/edit-profile', regularFunctions, async function (req, res) {
let email = req.body.email
let password_current = req.body.password_current
connection.query('SELECT * FROM accounts WHERE id = ?', req.body.id, async function (err, results) {
if (results.length > 0) {
bcrypt.compare(password_current, results[0].password, async function (err, isMatch) {
if (err) {
res.send('Unable to save settings')
res.end();
throw err
} else if (!isMatch) {
res.send('Password doesn\'t match.')
res.end();
} else {
let changed = []
// Password matches
if (req.body.password_new) {
let newPassword = req.body.password_new
let hashed_password = await hashPassword(newPassword)
connection.query('UPDATE accounts SET password = ? WHERE id = ?', [hashed_password, req.body.id], async function (error, results) {
if (results.affectedRows && results.affectedRows > 0) {
changed.push('password')
} else {
res.send('Unable to save settings')
res.end();
}
})
}
if (req.body.license_key) {
let newKey = req.body.license_key
axios.get(`https://voltcloud.net/api/hosting/check-key/${newKey}`, {
headers: {
authorization: 'Y1wUo3joP99JHiGM2orji0UYTey9gdqY'
}
}).then(function (response) {
let data = response.data
if (typeof data === 'object') {
if (data.active === 1) {
axios({
method: 'post',
url: `https://voltcloud.net/api/hosting/activate-key/${newKey}`,
headers: {
authorization: 'Y1wUo3joP99JHiGM2orji0UYTey9gdqY'
}
}).then(async function (response) {
if (response.data === 'Success') {
connection.query('UPDATE accounts SET license_key = ? WHERE id = ?', [newKey, req.body.id], async function (error, results) {
if (results.affectedRows && results.affectedRows > 0) {
changed.push('license key')
} else {
res.send('Unable to save settings')
res.end();
}
})
} else if (data === 'License already active!') {
res.send('License key is already active!')
res.end();
} else if (data === 'Failed to update key.') {
res.send('Unable to save settings')
res.end();
} else {
res.send('Unable to save settings')
res.end();
}
});
}
}
})
}
connection.query('UPDATE accounts SET email = ? WHERE id = ?', [email,req.body.id], async function (error, results) {
if (results.affectedRows && results.affectedRows > 0) {
changed.push('email')
} else {
res.send('Unable to save settings')
res.end();
}
});
let finalTxt = 'Successfully changed, '
if (changed.length > 1) {
changed.forEach(function (txt, index) {
if (index === 0) {
finalTxt = finalTxt + txt
} else if (index === 2) {
finalTxt = finalTxt + `and ${txt}.`
}
})
} else if (changed.length === 1) {
finalTxt = `Successfully changed ${changed[0]}.`
}
res.send(finalTxt)
res.end();
}
})
}
})
});}
I know this might seem like a very easy problem to some expert coders, but I am sort of new to this whole async and synchronous thing. Why is it that the "changed" array doesn't update even though it's being pushed to after the functions run? What I'm trying to do is have it only return one string that can be shown on the client-side but it doesn't seem to be changing it and only returning the "Successfully changed, "
This function confused me, as it has a lot of responsabilities as Mike 'Pomax' Kamermans pointed out, but I found the problem:
The connection.query method is non-blocking, meaning it will not wait for it's execution to end for it to advance to the next instructions.
When you are using async methods and Promise, it's nice to try and keep consistency over the methods (avoid mixing callback functions and async/await). I've refactored it over what it should look like if using async/await:
app.post('/api/edit-profile', regularFunctions, async function (req, res) {
let email = req.body.email
let password_current = req.body.password_current
let results = await executeQuery(connection, 'SELECT * FROM accounts WHERE id = ?', [req.body.id]);
if (results.length > 0) {
let isMatch = await comparePassword(password_current, results[0].password);
if (!isMatch) {
throw new Error(`Password doesn't match`);
}
let changed = []
// Password matches
if (req.body.password_new) {
let newPassword = req.body.password_new
let hashed_password = await hashPassword(newPassword)
let results = await executeQuery(connection, 'UPDATE accounts SET password = ? WHERE id = ?', [hashed_password, req.body.id]);
if (results.affectedRows && results.affectedRows > 0) {
changed.push('password')
} else {
throw new Error('Unable to save settings');
}
}
if (req.body.license_key) {
let newKey = req.body.license_key
let response = await axios.get(`https://voltcloud.net/api/hosting/check-key/${newKey}`, {
headers: {
authorization: '<redacted>'
}
});
let data = response.data
if (typeof data === 'object') {
if (data.active === 1) {
let response = await axios({
method: 'post',
url: `https://voltcloud.net/api/hosting/activate-key/${newKey}`,
headers: {
authorization: '<redacted>'
}
})
if (response.data === 'Success') {
let results = await executeQuery(connection, 'UPDATE accounts SET license_key = ? WHERE id = ?', [newKey, req.body.id]);
if (results.affectedRows && results.affectedRows > 0) {
changed.push('license key')
} else {
throw new Error('Unable to save settings');
}
} else if (data === 'License already active!') {
throw new Error('License key is already active!');
} else if (data === 'Failed to update key.') {
throw new Error('Unable to save settings');
} else {
throw new Error('Unable to save settings');
}
}
}
}
let results = await executeQuery(connection, 'UPDATE accounts SET email = ? WHERE id = ?', [email,req.body.id]);
if (results.affectedRows && results.affectedRows > 0) {
changed.push('email')
} else {
throw new Error('Unable to save settings');
}
let finalTxt = 'Successfully changed, '
if (changed.length > 1) {
changed.forEach(function (txt, index) {
if (index === 0) {
finalTxt = finalTxt + txt
} else if (index === 2) {
finalTxt = finalTxt + `and ${txt}.`
}
})
} else if (changed.length === 1) {
finalTxt = `Successfully changed ${changed[0]}.`
}
res.send(finalTxt)
res.end();
}
});
function executeQuery(conn, sql, params) {
return new Promise((resolve, reject) => {
conn.query(sql, params, function (err, data) {
if (err != null) {
return reject(err);
}
return resolve(data);
});
});
}
function comparePassword(val1, val2) {
return new Promise((resolve, reject) => {
bcrypt.compare(val1, val2, function (err, isMatch) {
if (err != null) {
return reject(err);
}
resolve(isMatch);
});
})
}
Notice that we're not using callback functions at all, and even where we don't have native Promise-based functions (i.e. mysql connection), we're delegating to a function that proxies the callback to deliver a Promise and keep consistency over the final implementation.
The original code isn't waiting for the two if branches to complete before sending the response. It's hard to structure code like this in callbacks, due to the nesting.
Try using async functions and await wherever possible. It allows for much more readable code and error handling is much easier. So this answer is more code review than a simple fix for your issue.
Split out some generic helper code that will be useful in other routes:
// Generate errors for the web, with context
function responseError(message, status, data){
const error = new Error(message)
error.status = status
for (const key in data){
error[key] = data[key]
}
return error
}
// Turn mysql callbacks into promises (or use util.promisify)
async function runQuery(query, values){
return new Promise((resolve, reject) => {
connection.query(query, values, function(error, results){
if (error) return reject(error)
return resolve(results)
})
})
}
async function runUpdateQuery(query, values){
const results = await runQuery(query, values)
if (!results) throw responseError('No update result', 500, { query })
if (!results.affectedRows) throw responseError('No affected rows', 400, { query })
return results
}
The code from the two if conditions can be easily separated, as well as the other account operations.
async function apiAuthUserId(id, password){
const results = await runQuery('SELECT * FROM accounts WHERE id = ?', id)
if (!results.length) throw responseError('No account', 400, { id })
const isMatch = await bcrypt.compare(password_current, results[0].password)
if (!isMatch) throw responseError('Password doesn\'t match', 400)
return true
}
async function apiUpdatePassword(id, password){
let newPassword = req.body.password_new
let hashed_password = await hashPassword(newPassword)
await runUpdateQuery('UPDATE accounts SET password = ? WHERE id = ?', [hashed_password, req.body.id])
return id
}
async function apiUpdateEmail(id, email){
await runUpdateQuery('UPDATE accounts SET email = ? WHERE id = ?', [email, id])
return email
}
async function apiUpdateLicenseKey(id, licenseKey){
const response_license = await axios.get(`https://voltcloud.net/api/hosting/check-key/${licenseKey}`, {
headers: {
authorization: 'somekey'
}
})
const data = response_license.data
if (!data) {
throw responseError('No license key response data', 500, { response: response_license })
}
if (data.active !== 1) {
throw responseError('License key not active', 400, { key: licenseKey })
}
const response_activate = await axios({
method: 'post',
url: `https://voltcloud.net/api/hosting/activate-key/${licenseKey}`,
headers: {
authorization: 'somekey'
}
})
switch (response_activate.data){
case 'License already active!':
throw responseError('License key is already active!', 400, { response: response_activate })
case 'Failed to update key.':
throw responseError('Unable to save settings!', 400, { response: response_activate })
case 'Success':
await runUpdateQuery('UPDATE accounts SET license_key = ? WHERE id = ?', [licenseKey, req.body.id])
return licenseKey
default:
throw responseError('Unable to save settings!', 500, { response: response_activate })
}
}
Then your route code can be a bit cleaner and show what needs to be done, rather than how to do it all.
app.post('/api/edit-profile', regularFunctions, async function (req, res) {
const changed = []
try {
const { id, email, password_current } = req.body
await apiAuthUserId(id, password_current)
// Password matches
if (req.body.password_new) {
await apiUpdatePassword(id, req.body.password_new)
changed.push('password')
}
// License key
if (req.body.license_key) {
await apiUpdateLicenseKey(id, req.body.license_key)
changed.push('license key')
}
await apiUpdateEmail(id, email)
changed.push('email')
let finalTxt = `Successfully changed ${changed.join(' and ')}.`
res.send(finalTxt)
}
catch (error) {
// If your not using transactions, might need to deal with partial `changed` responses here.
console.error('Error /api/edit-profile', error)
res.status(error.status||500).send(`Error: ${error.message}`)
}
});

Express js - Promise pending in database connection

In my login form I have an option that allow me to select which database I want to connect, so I'm trying to create a function to do that.
In db config file I have a function named setDB that go to another function named makeConnection sending the dbname as parameter and handle the result of this function.
The problem is that second function doesn't return any result but a promise pending.
In this function I have 3 return that means error that will be handle in setDB function but this not work.
var mssql = require('mssql');
module.exports =
{
setDB: (req, res) =>
{
console.log('Prepare to connect to ' + req);
var result = makeConnection(req);
console.log(result);
if(result == 0)
{
res.send('Unknown database!');
}
else if(result == 1)
{
res.send('Error trying to connect!')
}
else if(result == 2)
{
res.send('Connection done!')
}
//return connection;
}
}
function makeConnection(dbname)
{
console.log('Start connection....');
return new Promise(function(resolve, reject) {
console.log('Database: '+ dbname);
var configs = {
Emp1: {
user: "us",
password: "pass",
server: "ip",
database: "Emp1"
},
Emp2: {
user: "us",
password: "pass",
server: "ip",
database: "Emp2"
}
};
var config = configs[dbname];
if(config == undefined)
{
return 0;
}
var connection = new mssql.Connection(config);
connection.connect(function(err)
{
if (err) {
console.log(err);
reject(err);
return 1;
} else {
resolve(connection);
console.log('Database Connected!');
return 2;
}
});
});
}
Console Print:
What is the better way to do this?
I want to do a single connection and not do a connection in each request... And for example it have to be prepare to be used by 2 user in different databases at same time.
Which is the better way to do that?
Thank you
makeConnection() returns a Promise. So naturally when you call:
var result = makeConnection(req);
console.log(result);
It will result in logging a pending promise. That's what is should do. Your function immediately returns a pending promise that will resolve once the async operations have returned. But you are not waiting for them before trying to log the results. You need to use the promise's then() function to get the results when they are ready:
makeConnection(req)
.then(result => {
console.log(result);
if(result == 0)
{
res.send('Unknown database!');
}
else if(result == 1)
{
res.send('Error trying to connect!')
}
else if(result == 2)
{
res.send('Connection done!')
}
}

Again about async/await in javascript

My function looks like this now:
var GetImages = async() => {
var images_array = [];
await request ({
url: `https://api.tumblr.com/v2/blog/nameblog/posts?api_key=${process.env.TUMBLR_KEY}&type=photo`,
json: true
}, (error, response, body) => {
if(error){
console.log('Unable to connect');
}else if(body.meta.status === "ZERO_RESULTS"){
console.log('Uable to find that address.');
}else if(body.meta.status === 200){
body.response.posts.forEach(function(obj) {
obj.photos.forEach(function(photo) {
if(photo.original_size.width>photo.original_size.height){
images_array.push(photo.original_size.url);
console.log("dawdaw");
}
});
});
//callback(images_array);
}
});
return images_array;
}
I have no idea, how return my array after i'll fill it with values. With callback it works fine, but i wanna do it with async/await methid in right way. Thank you for help.
create method to return promise for request and use that method with await
requestPromise = () => {
return new Promise(function(resolve, reject) {
request({
url: `https://api.tumblr.com/v2/blog/nameblog/posts?api_key=${process.env.TUMBLR_KEY}&type=photo`,
json: true
}, (error, response, body) => {
if (error) {
console.log('Unable to connect');
reject();
} else if (body.meta.status === "ZERO_RESULTS") {
console.log('Uable to find that address.');
reject();
} else if (body.meta.status === 200) {
body.response.posts.forEach(function(obj) {
obj.photos.forEach(function(photo) {
if (photo.original_size.width > photo.original_size.height) {
images_array.push(photo.original_size.url);
console.log("dawdaw");
}
});
});
resolve(images_array)
}
});
});
}
var GetImages = async() => {
try
{
images = await requestPromise();
return images;
}
catch(e){return [];}
}

Callback was already called async parallel

Hi I am trying to use the Async module to retrieve two users and do some processing after they have both been retrieved however I keep getting the error message: Callback was already called. Below is the code i currently have:
app.get('/api/addfriend/:id/:email', function(req, res) {
var id = req.params.id;
var friendEmail = req.params.email;
async.parallel([
//get account
function(callback) {
accountsDB.find({
'_id': ObjectId(id)
}, function(err, account) {
console.log(id);
if (err || account.length === 0) {
callback(err);
}
console.log(account[0]);
callback(null, account[0]);
});
},
//get friend
function(callback) {
accountsDB.find({
'email': friendEmail
}, function(err, friend) {
console.log(friendEmail);
if (err || friend.length === 0 || friend[0].resId === undefined) {
callback(err);
}
console.log(friend[0]);
callback(null, friend[0].resId);
});
}
],
//Compute all results
function(err, results) {
if (err) {
console.log(err);
return res.send(400);
}
if (results === null || results[0] === null || results[1] === null) {
return res.send(400);
}
//results contains [sheets, Friends, Expenses]
var account = results[0];
var friend = results[1];
if (account.friends_list !== undefined) {
account.friends_list = account.friends_list + ',' + friend;
}
else {
account.friends_list = friend;
}
// sheetData.friends = results[1];
accountsDB.save(
account,
function(err, saved) {
if (err || !saved) {
console.log("Record not saved");
}
else {
console.log("Record saved");
return res.send(200, "friend added");
}
}
);
}
);
});
Any help would be appreciated.
Add else statement to your code, because if you get error, your callback executes twice
if (err || account.length === 0) {
callback(err);
} else {
callback(null, account[0]);
}
The docs from async actually say:
Make sure to always return when calling a callback early, otherwise
you will cause multiple callbacks and unpredictable behavior in many
cases.
So you can do:
return callback(err);

Categories

Resources