I anticipate a callback hell is beginning to form in my code so I decided to start using promises. But I can't wrap my head around implementing it. For example I have a function:
DB.prototype = {
findUser: function (username) {
this._pool.getConnection(function (err, connection) {
if (err) { return callback(true, false); }
connection.query('SELECT password_hash, password_salt FROM users WHERE email = ? AND admin = 1', username,
function (err, rows) {
connection.release();
if (rows.length === 1) { callback(false, rows[0]); }
else { callback(false, false); }
});
connection.on('error', function () { callback(true, false); });
});
}
};
How would I adapt this to using promises instead of callbacks? And how would I use this adapted db.findUser() ?
EDIT:
I got something working. It looks like this:
DB.prototype = {
getConnection: function() {
return this._pool.getConnectionAsync();
}
}
And the usage:
Promise.using(db.getConnection(), function(connection) {
return connection.queryAsync("SELECT password_hash, password_salt FROM users WHERE email = ? AND admin = 1", "exampleUser")
.then(function(rows) {
connection.release();
console.log("is there a row?", rows.length === 1, rows);
// do something with results
});
}).catch(function(err) {
// This is only run if an error is thrown
console.log("error is", err);
});
Is this a good implementation or could something be improved?
Related
Insted of using setTimeout, what should I use after foreach complete?
app.post('/grid', function(req, res){
getResults(req.body.idarray, function(callback){
res.send(callback);
});
});
function getResults(userIds, callback) {
var totalresult = [];
userIds.forEach(function (user) {
sequence
.then(function (next) {
db.query('SELECT given FROM books WHERE user_id = ?', [user.userId], function (err2, result) {
if (err2) throw err2;
next(err, result);
});
})
.then(function (next, err, books) {
db.query('SELECT received FROM encycs WHERE user_id = ?', [user.userId], function (err3, result2) {
if (err3) throw err3;
next(err, result2, books);
});
})
.then(function (next, err, books, encycs ) {
Calculation(books, encycs, function (cb) {
totalresult.push(cb);
});
next();
});
});
setTimeout(function() {
console.log(totalresult); // output ok.
return callback(totalresult); // returning as expected
}, 2000);
}
I dont know what totalresult.length is. So i can't check the length.
So, according to your use case you need to call callback somehow and pass totalresult into it, because that what your external code, code in the route expected.
To do that, you can call callback before calling next of the third .then statement. Like that.
...
.then(function (next, err, books, encycs ) {
Calculation(books, encycs, function (cb) {
totalresult.push(cb);
});
callback(totalresult);
next();
//console.log(totalresult); //output OK.
});
This might work.
Update 1
It is hard to follow with your code. Can't catch up the logic of it. I would propose you Promises approach. I prepared that solution, that might work. It might contain little errors, but it represents the main idea of what you are trying to achieve, and how it can be done.
app.post("/grid", (req, res) => {
getResults(req.body.idarray)
.then(data => {
res.status(200).json(data);
})
.catch(err => {
console.error("Error occured", err);
res.status(500).json(err);
});
});
function getResults(userIds) {
let promises = userIds.map(loadCalculation);
//this will wait until all loadings are done
return Promise.all(promises);
}
function loadCalculation(user) {
//parallel loading of the books and encycs
return Promise.all([loadBooks(user), loadEncycs(user)])
.then(results => {
let books = results[0];
let encycs = results[1];
let totalresult = [];
Calculation(books, encycs, function (cb) {
totalresult.push(cb);
});
return totalresult;
});
}
function loadBooks(user) {
return makeQuery('SELECT given FROM books WHERE user_id = ?', user);
}
function loadEncycs(user) {
return makeQuery('SELECT received FROM encycs WHERE user_id = ?', user);
}
function makeQuery(query, user) {
return Promise((resolve, reject) => {
db.query(query, [user.userId], function (err, result) {
if(err) {
reject(err);
} else {
resolve(result);
}
});
});
}
Please, note that this is not really performant way to load the data from database, at least, I'm sure that you there is a possibility to load all the books and encycs with a single query, because you are using SQL, and it is really flexible language.
I have a question about asynchronous function. Here my function "My_function":
function My_function (my_name, callback){
stmt = db.prepare ("SELECT number_table1 from my_table1 WHERE user=?");
stmt.bind(my_name);
stmt.get(function(error,row){
if(error){
throw err;
}
else{
if(row){
callback(number_table1);
}
else{
console.log("error");
}
}
});
}
Work fine but I have 2 tables and I need do other query and I need add two numbers so... in my function I need do too this query:
stmt = db.prepare ("SELECT number_table2 from my_table2 WHERE user=?");
and finally return back in my callback "number_table1 + number_table2".
Somebody know how to solve it? Thanks in advance.
Best regards!
In cases like your's I like to use the async module, because the code will be more legible. Example:
var async = require('async');
function My_function(my_name, callback) {
var stmt = db.prepare("SELECT number_table1 from my_table1 WHERE user=?");
stmt.bind(my_name);
stmt.get(function (error, row) {
if (error) {
callback(error, null);
} else {
if (row) {
callback(null, number_table1);
} else {
callback(new Error("row not found"), null);
}
}
});
}
//if you need the results in a specific order, you can use "series" instead of "parallel".
async.parallel(
//array of functions
[
function (callback) {
My_function('firstName', callback);
},
function (callback) {
My_function('secondName', callback);
}
],
//results when all functions ends
//the results array will equal [number_table1, number_table2], but the order can be different, because of the parallelism
function (err, results) {
if (err) {
//handle the err
} else {
//do something
}
}
);
Docs:
http://caolan.github.io/async/docs.html#parallel or
http://caolan.github.io/async/docs.html#series
You need to synchronize the functions so that you can be sure both their results are ready before calling back. You can do this using promises: https://www.promisejs.org/
Make two regular functions (no callbacks), one for each query (function1, function2)
Make both return a promise
Then you can do
function My_function(my_name) {
var value1;
function1(my_name)
.then(function(resultFromFunction1) {
value1 = resultFromFunction1;
return function2(my_name);
})
.then(function(resultFromFunction2) {
var result = value1 + resultFromFunction2;
return result;
});
}
}
Make sure to catch errors and handle different outcomes, what I presented is its simplest form.
Update
Here is an example of a function doing a query and returning a promise
function1 = function(user) {
return new Promise(function (resolve, reject) {
pool.getConnection(function (err, connection) {
if(err) {
reject ({status : false, message : "Error in connection database"});
} else {
connection.query('SELECT number_table1 from my_table1 WHERE user=?', [user], function(err, rows){
connection.release();
if(!err) {
resolve ({status: true, message: rows});
} else {
reject ({status: false, message: err});
}
});
}
});
});
}
Make the table names function parameters. Convert that function to use async/await or promise. Use Promise.all to run both queries.
I am currently doing an API in Node.JS with the framework Sails.js. I am using promises for the first time and I have some troubles to sync my promises like I want.
My main function is the following :
createCard: function(req, res) {
checkIfUserHasStripeAccount(req.user)
.then(addCreditCardToStripeAccount())
.then(function cardCreated() {
res.send(200, {
msg: 'Card created'
});
})
.catch(function handleError(err) {
res.send(err.httpCode, err.msg);
})
},
Obviously I can't add a credit card to a stripe account if the user doesn't have one.
The function checkIfUserHasStripeAccount() checks if the account exists and if not, create it.
Here is the code for this part :
function checkIfUserHasStripeAccount(user) {
var deferred = q.defer();
if (!user.idStripe) {
createStripeAccountToUser(user)
.then(function(savedUser) {
deferred.resolve(savedUser);
})
.catch(function(err) {
deferred.reject(err);
})
} else {
deferred.resolve(user);
}
return deferred.promise;
}
function createStripeAccountToUser(user) {
var deferred = q.defer();
var jsonUserToCreate = {
description: user.firstname + ' ' + user.surname,
email: user.email
};
stripe.customers.create(jsonUserToCreate, function(err, customer) {
if (err) {
deferred.reject({
httpCode: 500,
msg: 'some error'
});
} else {
user.idStripe = customer.id;
user.save(function(err, savedUser) {
if (err) {
deferred.reject({
httpCode: 500,
msg: 'some error'
});
}
deferred.resolve(savedUser);
});
}
});
return deferred.promise;
}
The problem is that the .then(addCreditCardToStripeAccount()) is executed before checkIfUserHasStripeAccount() is finished.
I can't figure out why. I thought the .then(addCreditCardToStripeAccount()) would only be executed if it received a reject or resolve.
You are correct in your line of thought.
The problem is that you are invoking your function instead of referencing it:
.then(addCreditCardToStripeAccount())
should be:
.then(addCreditCardToStripeAccount)
I expect this to work:
createCard: function (req, res) {
checkIfUserHasStripeAccount(req.user)
.then(addCreditCardToStripeAccount)
.then(function cardCreated(){
res.send(200, {msg: 'Card created'});
})
.catch(function handleError(err) {
res.send(err.httpCode, err.msg);
})
},
For future, note that the () after the function name invokes the function, as order of execution in JS will evaluate it first due to being inside the then's ().
In promise chains, always invoke only the first function. Example:
function first () { /*...*/ } // All return promise.
function second() { /*...*/ }
function third () { /*...*/ }
first() // Invoked
.then(second) // Not invoked. second() will have been bad here.
.then(third);
I am mew to node js, I have something like this,
get_contacts(data, function(contacts) {
if (contacts.length) {
var count = contacts.length;
for (var i = 0; i < count; i++) {
result = {
id: contacts[i].id,
name: contacts[i].name,
sent1: get_sent(data.userId, contacts[i].id, function(resp) {
result.sent = resp.count;
}),
}
result1[i] = result;
}
output = {
contacts: result1,
}
} else {
output = {
error: "No Contacts.",
}
}
res.writeHead(200, {'content-type': 'text/html'});
res.end(JSON.stringify(output));
});
get_contacts is a callback function which will return contact list.result1 & result are objects. Now value for sent should come from a function get_sent, and get sent is like this
function get_sent(userId, contactId, callback) {
pool.getConnection(function(err, connection) {
connection.query("my query here", function(err, rows) {
connection.release();
if (!err) {
callback(rows);
} else {
console.log(err)
}
});
});
}
But im not getting any value since nodejs. since nodejs is async it is not waiting for the function to return value. I know, im doing it in wrong way. Please help
You need to use a callback. In simple words is a function that you'll execute after something happens. You should read more about that. You should get a book about javascript but you can start reading here for example.
About your case, you could solve it like this
//Asumming that you object `result` is global.
result = {
id: contacts[i].id,
name: contacts[i].name,
sent: -1 //Some default value
}
//Just to put the code into a function, you have to put it where you need
function constructObject (){
get_sent(uId, cId, function(err, total){
if(err){
console.log("Something was wrong.", err);
}
result.sent = total;
//Here you have your object completed
console.log(result);
});
}
//You need to use a callback
function get_sent(uId, cId, callback) {
pool.getConnection(function(err, connection) {
//Note that I add an alias here
connection.query("SELECT count(*) as total FROM table_name", function(err, rows) {
connection.release();
if (!err) {
//I am returning the result of the query and a null error
callback(err, rows[0].total);
} else {
console.log(err);
//I am returning an error
callback(err);
}
});
});
}
//For example you could call this function here
constructObject();
And it depends of what are you doing exactly but Maybe you need a callback on your constructObject too.
With this as a URL:
'api/support-tag/name/myTagName'
This function works properly:
getByName: function (req, res) {
model.Shared_SupportTag.findOne({name: req.params.name}).exec(function (err, results) {
if (err) {
return res.status(400).send({
message: errMsg.Util_ErrorMsg.getErrorMessage(err)
});
}
res.send(results);
})
}
But when I try to call a similar function from within the node server:
supportDoc.category = GetById(item.category);
function GetById(name){
model.Shared_SupportTag.findOne({name: name}).exec(function(err, result){
if(err){
console.log(err)
}else{
console.log(result);
}
})
}
The function does not execute, nor does the error catch, intellisense shows:
err= Reference error; err is not defined
result = Reference error; result is not defined
All I am trying to accomplish is a function call from within the server and not via a URL.
Any solution here? Thanks in advance
In the case of the findOne() method, the positive response (sans error) will either hold a mongoose object or null.
If the same query had been sent using just find(), the result would have been an empty array.
function GetById(name){
model.Shared_SupportTag.findOne({name: name}).exec(function(err, result){
if(err){
console.log(err)
}else{
if (result) console.log(result); //Check whether object exists.
else console.log('Not found!');
}
})
}
Solved:
model.Shared_SupportDoc.find({}).exec(function (err, collection) {
var supportDocs = require('../../data/_seed/support/supportDocs.json');
if (collection.length === 0) {
supportDocs.forEach(function (item) {
var supportDoc = new model.Shared_SupportDoc;
supportDoc.title = item.title;
supportDoc.created = item.date;
supportDoc.icon = item.icon;
supportDoc.likeCount = item.likeCount || 7;
-----> // requires callback - ie asynchronous
GetByName(item.category, function(tagId) {
supportDoc.categoryId = tagId;
-----> // must put save in the callback
supportDoc.save(function (err) {
if (err) {
console.log(supportDoc.categoryId)
console.log('Error: ' + err);
} else {
console.log('Support Doc Seed Complete');
}
});
});
})
}
});}
function GetByName(name, next) {
model.Shared_SupportTag.findOne({name : name}).exec(function (err, result) {
if (!result) {
console.log('Not Found');
next();
} else {
console.log(result._id);
next(result._id);
}
});}