I'm having trouble understanding how to create functions that would return in the format of (err, result) for an Express app.
My current db query function is:
pool.query(
'SELECT id FROM users WHERE email = ? LIMIT 1',
[email],
(results) => { // I'd like this to be (err, results)
if(results instanceof Error){...}
}
})
In my db.js file, pool looks like this:
module.exports = {
query: (query, args, cb) => {
pool.getConnection( (err, connection) => {
if(err){
new Error('No database connections available in pool')
} else {
connection.query(query, args, (error, results, fields) => {
connection.release()
// I got a MySQL error here and I'd like to handle it in my callback function
if(error){
new Error('Bad query')
} else {
cb(results)
}
})
}
})
}
}
For this and other functions, I'd like to return a proper Error if there is one, and have my callback listen for err, result as parameters.
I tried using new Error('Bad query') but that came back as the first variable in my callback no matter what (which is how I ended up with instanceof Error.
How do you structure a callback and response so that your callback can be in the err, result format and check for/handle errors properly on functions you're creating? (I understand how to use it for modules already in this format - I'm talking about writing/formatting your own code.)
Thanks!
You can do it like this:
module.exports = {
query: (query, args, cb) => {
pool.getConnection( (err, connection) => {
if(err){
cb(new Error('No database connections available in pool'));
} else {
connection.query(query, args, (error, results, fields) => {
connection.release();
// I got a MySQL error here and I'd like to handle it in my callback function
if(error){
cb(new Error('Bad query'));
} else {
cb(null, results);
}
});
}
});
}
}
You always pass the error value as the first argument to the callback and, if there is a result, you pass it as the second. Then, within the callback, you check to see if err is non-null and, if so, there is an error. If it's null, then the second argument contains the result.
Note that by not returning or including the actual err value that the database gave you, you may be hiding useful information (like why the query failed).
Then, where you use this, you do something like this:
let query = 'SELECT id FROM users WHERE email = ? LIMIT 1';
pool.query(query, [email], (err, result) => {
if (err) {
// handle error here
} else {
// process result here
}
});
Related
In the below code, users.push used within ‘db.each’ wont work. However, if I move ‘users.push’ outside then it seems to work.
How can I push the new objects from db.each into the users array?
let db = new sqlite3.Database('./db/main.db', (err) => {
if (err) console.error(err.message);
console.log('Connected to the main database.');
});
var users = [];
db.serialize(() => {
db.each(`SELECT email, name FROM users`, (err, row) => {
if (err) console.error(err.message);
let user = {
email: row.email,
name: row.name
}
users.push(user);
});
});
console.log(JSON.stringify(users));
db.close();
I am using express and sqlite3 node packages.
It's because db.serializeand db.each are asynchronous functions (and return immediately, thus executing console.log before the db callbacks are executed).
Here should be a working example :
db.serialize(() => {
db.each(`SELECT email,
name
FROM users`, (err, row) => {
if (err) {
console.error(err.message);
}
let user = {
email : row.email,
name : row.name
}
users.push(user);
console.log(JSON.stringify(users));
db.close();
});
});
First error: asynchronicity not handled properly
As Antoine Chalifour pointed out, you call console.log(JSON.stringify(users)); before users gets modified in the asynchronous callback. Refer to his answer for fix and explanations.
Second error: errors not handled
You wrote if (err) { console.error(err.message); } then go on with the rest of the function. That is bad, because an error might happen and you'd just continue with your program. You should instead write something like:
if (err) {
console.error(err);
return;
}
or:
if (err) throw err;
Just started to learn express js framework ,here is my simple database query execution part its invoked with this url localhost:3000/api/test.
db.query('SELECT * FROM user', function (error, results, fields) {
if (error) throw error;
console.log('The result is:', results[0].id);
return results;
});
Does it really asynchronous?? suppose another user request this url does he need to wait for the previous query execution??.
I've heard about async package ,but don't know how this is applicable in my case
UPDATE
I got proper result in console.log(); but when i return the result i got undefined error
Here is my model.js
module.exports = {
getUser:function () {
db.query('SELECT * FROM user', function (error, results, fields) {
if (error) throw error;
console.log('The result is: ', results[0].id);
});
}
}
From my controller.js
var model = require('../models/user.js');
module.exports = {
getData : function(req, res){
//invoke model
console.log(model.getUser());
}
}
Node is non-blocking and will serve this request as and when it's called.
If another user hits this endpoint then it will execute again regardless if the first query has completed or not (unless the SQL has locked the table, in which case all consecutive connections/queries will wait and may timeout because of it). This happens on a connection basis.
You should make sure to check your SQL server (MySQL?) configs here to make sure there are enough max_connections to be able to cope with whatever load you are expecting.
Remember that the biggest bottleneck to an application is usually the database.
Your query above will need a callback to return the data asynchronously.
db.query('SELECT * FROM user', function (error, results, fields) {
if (error) throw error;
console.log('The result is:', results[0].id);
//cb=callback function passed in to context
if (cb) cb(results);
});
Updated answer from updated question
In your model.js:
module.exports = {
getUser:function (cb) {
db.query('SELECT * FROM user', function (error, results, fields) {
if (error) throw error;
console.log('The result is: ', results[0].id);
if (cb) cb(results);
});
}
}
In your controller.js:
module.exports = {
getData : function(req, res){
//invoke model
model.getUser(function(results) {
console.log(results);
});
}
}
When you deal with callback, the safe and clean way to handle them is Promises. It's now standard in JavaScript and don't require any module.
And yes it is asynchronous. Behind, there'll be network access and dialogs with the database server. Only when they're done chatting will the callback be called.
module.exports = {
getUser: function () {
// wrap asynchronously called callback in Promise
new Promise((resolve, reject) => {
db.query("SELECT * FROM user", (error, results, fields) => {
if (error) {
reject(error); // similar to "throw"
}
else {
resolve({ results, fields }); // similar to "return"
}
});
});
}
};
How do you use it:
Vanilla notation:
// similar to "try"
model.getUser()
.then((answer) => {
console.log("answer", answer);
})
// similar to "catch"
.catch((error) => {
console.log("error", error);
});
async-await notation (only available in last versions of nodejs & browsers):
// you must be in an async environement to use "await"
async function wrapper() {
try {
var answer = await model.getUser(); // wait for Promise resolution
console.log("answer", answer);
}
catch(error) {
console.log("error", error);
}
}
// async function return automatically a Promise so you can chain them easily
wrapper();
i want to simply handle error and get insertId after created new row on database, in my code which i get help from mysqljs/mysql official page, i can't do them, how can i fix my code to do them?
var post = {deviceImei: deviceInformation.deviceImei, created_at: currentDateTime, updated_at: currentDateTime};
var query = connection.query('INSERT INTO users SET ?', post, function (err, result) {
if (err)
throw err;
return result;
});
query.on('error', function (err) {
console.log(err)
}).on('fields', function (fields) {
console.log(fields)
}).on('result', function (row) {
console.log(row.insertId);
}).on('end', function () {
// all rows have been received
});
after insert or get some error i cant any message on shell by console
The correct way to do that is documented here:
connection.query('INSERT INTO users SET ?', post, function(err, result) {
if (err) throw err;
console.log(result.insertId);
});
You can use native ES6 promises supported in newest versions of node
var insertQuery = function(post) {
return new Promise(function(resolve, reject) {
connection.query('INSERT INTO users SET ?', post, function (err, result) {
if (err)
reject(error);
resolve(result);
}
});
}
The you can handle after
insertQuery(post)
.then(function(result) {
console.log(result.inserId);
})
.catch(function(error) {
console.log(err);
})
There are libs like 'q' or 'bluebird' which are easy to use too
Each time I throw a query, the nest depth increase by one, just like the code below. If I knew how to define a query as a function not in the action, the readability of my code would increase.
exports.getAll = function (req, res) {
client.query('SELECT * FROM tag', function (err, result, fields) {
client.destroy();
if (err) {
throw err;
}
var tag = result[0].tag;
client.query('SELECT COUNT(follow_id) AS following_tag_num FROM follow WHERE user_id = ?', [req.session.user.user_id], function (err, result, fields) {
client.destroy();
if (err) {
throw err;
}
res.render('hoge', {
title: 'Welcome to Hoge',
userInfo: req.session.user,
tag: tag,
following_tag_num: result[0].following_tag_num
});
});
});
}
Just make the handler a named function:
client.query(
'SELECT COUNT(follow_id) AS following_tag_num FROM follow WHERE user_id = ?',
[req.session.user.user_id],
handleResult
);
function handleResult(err, result, fields) {
client.destroy();
if (err) {
throw err;
}
res.render('hoge', {
title : 'Welcome to Hoge',
userInfo : req.session.user,
tag : tag,
following_tag_num: result[0].following_tag_num
});
}
You might look into several node flow control modules that are available to help curb the nesting. I like one called async. It provides a variety of ways to de-nest your nested code.
var async = require('async');
async.waterfall([
function(callback) {
client.query(sql, callback);
},
function(results, callback) {
// do something with results, then call callback
}],
function(err, data) {
// if any errors occur above, err is not null
// otherwise 'data' is whatever got passed to the last callback
});
async.waterfall takes a list of functions, and passes the results of each one on to the next, finally calling the second parameter, another function, with the final result. Results are passed not by returning them, but by a callback function. async also supports running several functions in parallel, in series, and a variety of different common patterns used in node.
How do you do a "join" (i know it is not the correct term) with an array of messages in mongoose?
I tried looping over all the messages and querying to get the user info but it is not working:
messages.forEach(function (message, index) {
User.findById(message.userId, function (err, user) {
messages[index].user = user
})
})
console.log(messages) // the user info is not attatched
So how is this accomplished with mongoose and node.js?
the biggest problem with your code is, that you assume the code to run synchronously - but it doesn't. it runs asynchronously. so messages is not yet set when you execute
console.log(messages);
do something like this instead:
var userIds = [id1, id2, id3];
User.find({"_id": {$in: userIds}}, function (err, users) {
console.log(users);
});
edit
ok, i see. you want to add the userInfo to the different messages.
easiest way to acieve this, is to use the async module: https://github.com/caolan/async
async.map(messages, getUserInfo, function (err, result) {
if (err) {
console.log(err);
return;
}
// log all msg with userinfo
console.log(result);
});
function getUserInfo (msg, callback) {
User.findById(msg.userId, function (err, user) {
if (err) {
callback(err);
return;
}
msg.user = user;
callback(null, msg);
});
}