I have a bot. It can input some text and return some word.
I would like to use MongoDB. Because Heroku can't store data.
So I add function.js that use mongoose.
console.log('data.functionswitch = ' + data.functionswitch);
console log is work fine. It can reply what i want.
return data.functionswitch;
but return data.functionswitch only return undefined when i call it in input.js/.
I have try async/await.
But it only stops working.
How can I improve it and make it work? Thank you.
-
-
2018/03/15 updated
function.js
function switchfind(id, name, callback) {
mongodb.functionSwitch.findOne({
groupid: id, functionname: name
}, function (err, data) {
if (err) {
console.log(err);
callback(null);
return;
}
else if (!data) {
console.log("No record found")
callback(null);
return;
}
console.log('date = ' + data);
console.log('data.functionswitch = ' + data.functionswitch);
callback(data.functionswitch);
return;
})
};
input.js
function parseInput(rplyToken, inputStr) {
//console.log('InputStr: ' + inputStr);
_isNaN = function (obj) {
return isNaN(parseInt(obj));
}
let msgSplitor = (/\S+/ig);
let mainMsg = inputStr.match(msgSplitor);
let trigger = mainMsg[0].toString().toLowerCase();
exports.mongoose.switchfind(mainMsg[1], mainMsg[2], function (functionswitch) {
console.log('functionswitch = ' + functionswitch)
if (functionswitch === null) {
console.log('HERE === NULL ')
}
if (functionswitch == 0) {
console.log('HERE != 0')
return;
}
else if (functionswitch != 0 ) {
console.log('HERE != 0')
if (inputStr.match(/\w/) != null && inputStr.toLowerCase().match(/\d+d+\d/) != null) return exports.rollbase.nomalDiceRoller(inputStr, mainMsg[0], mainMsg[1], mainMsg[2]);
}
})
}
update
const mongoose = require('mongoose');
let uristring = process.env.mongoURL ||
'mongodb://XXXXXXX';
mongoose.connect(uristring);
mongoose.connect(uristring, function (err, res) {
if (err) {
console.log('ERROR connecting to: ' + uristring + '. ' + err);
} else {
console.log('Succeeded connected to: ' + uristring);
// console.log('allswitch: ' + allswitch);
}
});
var functionSchema = new mongoose.Schema({
groupid: String,
functionname: String,
functionswitch: String
});
// Compiles the schema into a model, opening (or creating, if
// nonexistent) the 'PowerUsers' collection in the MongoDB database
var functionSwitch = mongoose.model('functionSwitchs', functionSchema);
The problem in your code is that you are using findOne as it was synchronous. You cannot simply return the data, you have to use a callback.
Here is a tutorial about callbacks.
Example of what it should look like :
// The find function
function switchfind(id, name, callback) {
mongodb.functionSwitch.findOne({
groupid: id,
functionname: name
}, function (err, data) {
// Handle error
if (err) {
callback(null);
return;
}
// Handle empty data
if (data == null) {
callback(null);
return;
}
// Handle with data
callback(data.functionswitch);
})
};
// How to call it
funcX() {
switchfind(id, name, function (functionswitch) {
if (functionswitch === null) {
// Handle the error
}
// Handle the data
});
}
Related
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}`)
}
});
I am trying to test a constructor in node.js that uses asynchronous code, to test out a feature in it. I know the asynchronous code works because I have already ran it, before I put in the feature, as if I were an end-user. In fact, I got it thanks to some user who answered another question I had
The feature
The User constructor has a userRole member, and some async code that checks userType specified against User.userTypes. If userType found, this.userRole is set to userType. Else, exception is thrown.
The code looks like this:
async.forEachSeries(
User.userTypes,
function(user_type, callback)
{
if (user_type == userType)
{
this.userRole = user_type;
return;
}
if (user_type == User.userTypes[User.userTypes.length - 1])
{
callback(helpers.invalidData("user_role"));
return;
}
callback(null);
},
function(err)
{
if (err)
{
if (DEBUG)
{
console.log("Error from constructor...");
console.log(JSON.stringify(err, null, '\t') + "\n");
}
throw err;
}
}
);
The rest of the constructor looks like:
function User(id, email, displayName, password, userType, deleted, cb)
{
var DEBUG = true;
var error = null;
this.userID = id;
this.email = email;
this.displayName = displayName;
this.deleted = deleted;
var self = this;
async.forEachSeries(
User.userTypes,
function(user_type, callback)
{
if (user_type == userType)
{
this.userRole = user_type;
return;
}
if (user_type == User.userTypes[User.userTypes.length - 1])
{
callback(helpers.invalidData("user_role"));
return;
}
callback(null);
},
function(err)
{
if (err)
{
if (DEBUG)
{
console.log("Error from constructor...");
console.log(JSON.stringify(err, null, '\t') + "\n");
}
throw err;
}
}
);
if (User.connectedToDatabase) this._password = password;
else
{
bcrypt.genSalt(10, function (e, salt) {
bcrypt.hash(password, salt, function (e, hash) {
if (!e)
{
self._password = hash;
if (DEBUG)
{
console.log("this._password ==" + self._password);
console.log("this.userID == " + self.userID);
}
if (typeof cb === 'function')
cb(null, this);
}
else
{
console.log("Error occurred: ");
console.log(e);
if (typeof cb === 'function')
cb(e);
}
})
});
}
}
User.connectedToDatabase = false;
User.BASIC = "basic user";
User.INVENTORY_MANAGEMENT = "inventory";
User.ADMIN = "admin";
User.userTypes = [ User.BASIC, User.INVENTORY_MANAGEMENT, User.ADMIN ];
User.prototype.userID = 0;
User.prototype.email = null;
User.prototype.displayName = null;
User.prototype._password = null;
User.prototype.userRole = User.BASIC;
User.prototype.deleted = false;
User.prototype.responseObject = function() {
return {
id: this.userID,
email: this.email,
displayName: this.displayName,
userType: this.userRole
};
}
My test
I write test() function that takes parameters to pass to User. If User constructed without error, it, along with some of its members are printed to console. Else, error is printed. Here is code:
function test(email, name, pass, type)
{
try
{
var a = new User(Math.round(Math.random() * 32),
email,
name,
pass,
type
);
console.log("Test user created: " + JSON.stringify(a.responseObject(), null, '\t') + "\n");
console.log("User._password == " + a._password);
console.log("\n");
}
catch (e)
{
console.log("User could not be created.\n" + JSON.stringify(e, null, '\t') + "\n");
}
/*async.waterfall([
function(callback){
var a = new User(Math.round(Math.random * 32),
email,
name,
pass,
type);
callback(null, a);
},
function(a, callback) {
console.log("Test user created: " + JSON.stringify(a, null, '\t') + "\n");
console.log("User._password == " + a._password);
console.log("User.userID == " + a.userID);
console.log("\n");
callback(null, a);
}
],
function(err, results)
{
console.log("results of test: " + JSON.stringify(results, null, '\t'));
if (err)
{
console.log("User could not be created.\n" + JSON.stringify(err, null, '\t') + "\n");
}
})*/
}
My test cases are as follows:
userType matching first element of User.userTypes
userType matching another element of User.userTypes (I picked the last one)
userType not matching any of User.userTypes
At runtime, after new User created, User._password is default value and not the value my asynchronous code comes up with for it. I suspect I have some async-sync error, but haven't been able to fix it.
Got it fixed, thanks to #Bergi's advice.
First thing I did: in that async.forEachSeries(), I changed any instance of this to self.
Second thing I did: replaced that asynchronous code (that was working!) to synchronous code. Instead of this:
bcrypt.genSalt(10, function (e, salt) {
bcrypt.hash(password, salt, function (e, hash) {
if (!e)
{
self._password = hash;
if (DEBUG)
{
console.log("this._password ==" + self._password);
console.log("this.userID == " + self.userID);
}
if (typeof cb === 'function')
cb(null, this);
}
else
{
console.log("Error occurred: ");
console.log(e);
if (typeof cb === 'function')
cb(e);
}
})
});
I simply said: this._password = bcrypt.hashSync(password, bcrypt.genSaltSync(10)); and all was good!
I have this code whereby i want to check if code input data is the same in the local db. This code works fine until it gets to where i have marked as code hangs or stops here. Once the code gets to the condition it runs perfectly and assigns notifier to be true but it doesnt come out of that function and is stuck there hence the remaining part of the code is not executed. Can anyone explain to me why ? I am building an Ionic, AngularJS app.
function checklocalDB(localdb, result) {
var d= $q.defer();
var identifier = false;
var notifier = false;
// var f = function(localdb, result){
if(localdb === false) {
console.log("inside localdb false")
var insert_into_table = "INSERT INTO preferences(description, value) VALUES ('username','" + result[0].username + "'), ('token','" + result[0].token.toString() + "')";
$cordovaSQLite.execute(db, insert_into_table).then(function (res) {
console.log("executedd")
var updateDB = "UPDATE preferences SET value='true' WHERE description='registered'";
$cordovaSQLite.execute(db, updateDB).then(function (res) {
console.log("executed")
identifier = true;
notifier = true;
//d.resolve(identifier)
var query = "SELECT id, description, value FROM preferences";
$cordovaSQLite.execute(db, query).then(function (res) {
}, function (err) {
console.error(err);
});
}, function (err) {
console.error(err);
});
});
}
else {
console.log("inside localdb true")
var dbNew = null;
var query = "SELECT id, description, value FROM preferences";
console.log(localdb)
$cordovaSQLite.execute(db, query).then(function (res) {
console.log("hhhhhhhhhhhhhh")
console.log(res.rows.item(2).value)
console.log(result[0].username)
if(res.rows.item(2).value != result[0].username) {
console.log("username different")
$cordovaSQLite.deleteDB("loanstreet_partners.db");
dbNew = $cordovaSQLite.openDB("loanstreet_partners.db");
$cordovaSQLite.execute(dbNew, "CREATE TABLE IF NOT EXISTS preferences (id integer primary key, description string, value string)").then(function (res) {
console.log("done")
var insert_into_table = "INSERT INTO preferences (description, value) SELECT 'registered' as registered, 'false' as value UNION SELECT 'logged_in', 'false'";
$cordovaSQLite.execute(db, insert_into_table).then(function (res) {
console.log("1st")
var insert_into_table = "INSERT INTO preferences(description, value) VALUES ('username','" + result[0].username + "'), ('token','" + result[0].token.toString() + "')";
$cordovaSQLite.execute(db, insert_into_table).then(function (res) {
console.log("2nd")
identifier = true;
notifier = true;
var updateDB = "UPDATE preferences SET value='true' WHERE description='registered'";
$cordovaSQLite.execute(db, updateDB).then(function (res) {
}, function (err) {
console.error(err);
});
});
}, function (err) {
console.error(err);
});
}, function (err) {
console.error(err);
});
}
else {
notifier = true;
console.log("im here")
return notifier;
// ***code hangs or stops here***
}
}, function (err) {
console.error(err);
});
}
// ***this is never executed because it still remains false***
if(notifier === true) {
console.log(identifier)
console.log(notifier)
d.resolve(identifier)
}
return d.promise;
// watch identifier when value change then only resolve
//d.resolve(identifier)
//return d.promise;
}
Any help appreciated
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);
I'm querying for a user object and performing a series of updates on the items within compared to incoming postdata (jsObject). I'm wondering how to completely remove an item from the object...specifically a Date object (user[0].birthDate)...before I make a save of the updated user object.
orm.User.find({ appId: appId, facebookUsername:usersFBUsername}).exec(function (error, user) {
if (error) {
console.log('Error in User Query: ' + error);
}
else if(Object.keys(user).length > 0) {
if(jsObject.name != null)
user[0].name = jsObject.name;
if(jsObject.email != null)
user[0].emailAddress = jsObject.email;
if(jsObject.birthDate != null && jsObject.birthDate.length > 0) {
user[0].birthDate = jsObject.birthDate;
}
else {
console.log('delete it');
//orm.User.update({_id:user._id}, {$pull:{birthDate:1}});
//delete user[0].birthDate;
}
}
user[0].save(function (error) {
if (error != null) {
console.log('An error has occurred while saving user:' + error);
response.end(results.getResultsJSON(results.ERROR, error));
}
else {
console.log(' [User Successfully Updated]');
response.end('{ "success": ' + JSON.stringify(user[0]) + ' }');
}
});
});
You can see in the commented code some attempts I've made which have not been successful. I even gave this a try after the save completed, which also did not work:
orm.User.update({appId: appId, facebookUsername:usersFBUsername},{$pull:{birthDate:deleteBirthDate}})
.exec(function(error){
if(error) {
console.log('oh well: ' + error);
}
else {
console.log('maybe maybe');
}
});
I appreciate any suggestions.
Chris
$pull is for removing values from arrays, but you can use $unset:
orm.User.update(
{_id : user._id},
{ $unset : { birthDate : 1 }},
function(err, numAffected) {
...
}
);
For reference:
https://docs.mongodb.com/manual/reference/operator/update/unset/