Consider a function like this:
function fetchUser(userId, cb) {
db.getUserById(userId, function(err, user) {
if (err)
return cb(err)
if (!user)
return // Pay attention to this line
return cb(null, user)
});
}
How would you promisify such a function.
Precisely, how would you deal with the simple return; statement that doesn't call the callback ?
Should this be a promise that stay in a pending state forever ? Doesn't feel right somehow
Related
I need to make a function like this.
function getDataFromCollection(collectionName) {
let data;
MongoClient.connect(url, function(err, db) {
if (err) throw err;
db.collection(collectionName).find({}).toArray(function(err, result) {
data = result;
console.log(result);
db.close();
});
});
return data;
}
The problem is that data is undefined, but when I execute console.log(result), it works. Any idea to help me. Thanks in advance.
There's a very simple explanation. The function(err, result) is essentially asynchronous and is not called immediately but after some time when the data is fetched from mongo. The function(err, result) is thus a callback. So, data is not set to result immediately but after a while. Now, you return data immediately and don't wait for it to be populated (inside the function(err, result) callback) so undefined is obviously returned.
The solution would be to use JavaScript's Promises which lets you use asynchronous code and callbacks. We return a Promise from getDataFromCollection and chain a .then when we call it. The function or callback that is passed to the then is executed when the promise is resolved inside the getDataFromCollectionfunction with the data that was passed to the resolve function. Thus, the callback inside then would be called when you receive the result.
All the code -
function getDataFromCollection(collectionName) {
return new Promise(function(resolve, reject) {
MongoClient.connect(url, function(err, db) {
if (err) {
reject(err);
return;
}
db.collection(collectionName).find({}).toArray(function(err, result) {
if (err) {
reject(err);
return;
}
console.log(result);
db.close();
resolve(result);
});
});
});
}
Consume the function like so.
getDataFromCollection("collection")
.then(function(result) {
// use result
})
.catch(function(err) {
console.log(err);
});
Read up about Promises from here.
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 am trying to build a login API using NodeJS, but my code is not doing what I expect it to. I am very new to js, promises and all so please simplify any answer if possible.
From what I can see in the output of my code, the first promise part does not wait until the function findUsers(...) is finished.
I have a routes file where I want to run a few functions sequentially:
Find if user exist in database
if(1 is true) Hash and salt the inputted password
... etc
The routes file now contains:
var loginM = require('../models/login');
var loginC = require('../controllers/login');
var Promise = require('promise');
module.exports = function(app) {
app.post('/login/', function(req, res, next) {
var promise = new Promise(function (resolve, reject) {
var rows = loginM.findUser(req.body, res);
if (rows.length > 0) {
console.log("Success");
resolve(rows);
} else {
console.log("Failed");
reject(reason);
}
});
promise.then(function(data) {
return new Promise(function (resolve, reject) {
loginC.doSomething(data);
if (success) {
console.log("Success 2");
resolve(data);
} else {
console.log("Failed 2");
reject(reason);
}
});
}, function (reason) {
console.log("error handler second");
});
});
}
And the findUser function contains pooling and a query and is in a models file:
var connection = require('../dbConnection');
var loginC = require('../controllers/login');
function Login() {
var me = this;
var pool = connection.getPool();
me.findUser = function(params, res) {
var username = params.username;
pool.getConnection(function (err, connection) {
console.log("Connection ");
if (err) {
console.log("ERROR 1 ");
res.send({"code": 100, "status": "Error in connection database"});
return;
}
connection.query('select Id, Name, Password from Users ' +
'where Users.Name = ?', [username], function (err, rows) {
connection.release();
if (!err) {
return rows;
} else {
return false;
}
});
//connection.on('error', function (err) {
// res.send({"code": 100, "status": "Error in connection database"});
// return;
//});
});
}
}
module.exports = new Login();
The output i get is:
Server listening on port 3000
Something is happening
error handler second
Connection
So what I want to know about this code is twofold:
Why is the first promise not waiting for findUser to return before proceeding with the if/else and what do I need to change for this to happen?
Why is error handler second outputed but not Failed?
I feel like there is something I am totally misunderstanding about promises.
I am grateful for any answer. Thanks.
Issues with the code
Ok, there are a lot of issues here so first things first.
connection.query('...', function (err, rows) {
connection.release();
if (!err) {
return rows;
} else {
return false;
}
});
This will not work because you are returning data to the caller, which is the database query that calls your callback with err and rows and doesn't care about the return value of your callback.
What you need to do is to call some other function or method when you have the rows or when you don't.
You are calling:
var rows = loginM.findUser(req.body, res);
and you expect to get the rows there, but you won't. What you'll get is undefined and you'll get it quicker than the database query is even started. It works like this:
me.findUser = function(params, res) {
// (1) you save the username in a variable
var username = params.username;
// (2) you pass a function to getConnection method
pool.getConnection(function (err, connection) {
console.log("Connection ");
if (err) {
console.log("ERROR 1 ");
res.send({"code": 100, "status": "Error in connection database"});
return;
}
connection.query('select Id, Name, Password from Users ' +
'where Users.Name = ?', [username], function (err, rows) {
connection.release();
if (!err) {
return rows;
} else {
return false;
}
});
//connection.on('error', function (err) {
// res.send({"code": 100, "status": "Error in connection database"});
// return;
//});
});
// (3) you end a function and implicitly return undefined
}
The pool.getConnection method returns immediately after you pass a function, before the connection to the database is even made. Then, after some time, that function that you passed to that method may get called, but it will be long after you already returned undefined to the code that wanted a value in:
var rows = loginM.findUser(req.body, res);
Instead of returning values from callbacks you need to call some other functions or methods from them (like some callbacks that you need to call, or a method to resolve a promise).
Returning a value is a synchronous concept and will not work for asynchronous code.
How promises should be used
Now, if your function returned a promise:
me.findUser = function(params, res) {
var username = params.username;
return new Promise(function (res, rej) {
pool.getConnection(function (err, connection) {
console.log("Connection ");
if (err) {
rej('db error');
} else {
connection.query('...', [username], function (err, rows) {
connection.release();
if (!err) {
res(rows);
} else {
rej('other error');
}
});
});
});
}
then you'll be able to use it in some other part of your code in a way like this:
app.post('/login/', function(req, res, next) {
var promise = new Promise(function (resolve, reject) {
// rows is a promise now:
var rows = loginM.findUser(req.body, res);
rows.then(function (rowsValue) {
console.log("Success");
resolve(rowsValue);
}).catch(function (err) {
console.log("Failed");
reject(err);
});
});
// ...
Explanation
In summary, if you are running an asynchronous operation - like a database query - then you can't have the value immediately like this:
var value = query();
because the server would need to block waiting for the database before it could execute the assignment - and this is what happens in every language with synchronous, blocking I/O (that's why you need to have threads in those languages so that other things can be done while that thread is blocked).
In Node you can either use a callback function that you pass to the asynchronous function to get called when it has data:
query(function (error, data) {
if (error) {
// we have error
} else {
// we have data
}
});
otherCode();
Or you can get a promise:
var promise = query();
promise.then(function (data) {
// we have data
}).catch(function (error) {
// we have error
});
otherCode();
But in both cases otherCode() will be run immediately after registering your callback or promise handlers, before the query has any data - that is no blocking has to be done.
Summary
The whole idea is that in an asynchronous, non-blocking, single-threaded environment like Node.JS you never do more than one thing at a time - but you can wait for a lot of things. But you don't just wait for something and do nothing while you're waiting, you schedule other things, wait for more things, and eventually you get called back when it's ready.
Actually I wrote a short story on Medium to illustrate that concept: Nonblacking I/O on the planet Asynchronia256/16 - A short story loosely based on uncertain facts.
Ok I have a JS method that uses Lodash and mongoose to find docs in a Mongoose collection. This looks fine, but it that appears to not finish the callback function before moving on to the next doc to look for. Below is my function:
importParts: function(participants, event_id, done){
_.forEach(participants, function (participant) {
var race = {event_id: event_id, chip_number_64: participant.chip_number_64};
Runner.findOne({contact_id: participant.contact_id}, function (err, doc) {
if (err) {
logger.error('CANNOT FIND AND UPDATE RUNNER BECAUSE OF: ', err);
done(err);
}
logger.info("IN FINDONE");
if (doc === null) {
logger.info("IN FINDONE1");
participant.races = [race];
Runner.create(participant, function (err, row, rowsAffected) {
if (err) {
logger.error('FAILED TO IMPORT PARTICIPANT: ', doc);
logger.error(err);
done(err);
}
logger.info("IN FINDONE2");
});
}
});
};
done(null);
}
For some reason the above code does not honor the callback function and appears to asynchronously return back to the main method that is calling this one. It's as if the callback is not being honored till after a set amount of time or something is happening asynchronously that shouldn't because I have everything wrapped in callbacks. So I am just trying to find out why the callback isn't completing when the query is executed? Also, this still happens even without the forEach iteration included in above code.
I'm getting "client is not defined". I can see client is not defined for the function but I'm not sure how to pass in client into the async. I don't think I'm actually returning the value of the each correct to pg_commit either or am I?
Basically I want to have an array of queries and loop over them and make them queries and then when all done commit those as a transaction.
var pg_conn_str = "postgres://postgres:5432#localhost/test2";
var pg = require ('pg');
var rollback = function (client, done) {
client.query ('ROLLBACK', function (err) {
return done (err);
});
};
var queries = ["INSERT INTO foo (bar) VALUES (4)",
"INSERT INTO foo (bar) VALUES (5)"];
pg.connect (pg_conn_str, function (err, client, done) {
if (err) throw err;
client.query ('BEGIN', function (err) {
if (err) return rollback (client, done);
process.nextTick (function() {
if (err)
console.log (err);
async.each (queries, pg_commit, function () {
client.query ('COMMIT', done);
console.log ('done');
});
}); //nextTick
}); //begin
}); //connect
function pg_commit (val) {
client.query (val, function (err) {
if (err) return rollback (client, done);
});
return (val);
}
The problem is that client variable is defined only within the callback function called by pg.connect method (as its param). But in pg_commit this name is meaningless - as this function is not defined within the scope of the callback function and doesn't have access to its name bindings.
Hence you have two solutions: either move pg_commit definition into the callback...
function (err, client, done) {
if (err) throw err;
var pg_commit = function(val) { client.query(val, function(err) { ... }};
// ...
... or leave it where it is now, but change it into a function generator:
function pg_commit_generator(client) {
return function(val) { client.query(val, function(){}); /*...*/ }
}
... then use this generator to create a function called by async.each:
async.each(queries, pg_commit_generator(client), function() {...});
Alternatively, you can just supply into async.each the anonymous function that calls pg_commit:
async.each(queries, function(val) { return pg_commit(val, client); }, ...);
... and adjust pg_commit accordingly (so it'll take client as a second param).