I'm new to NodeJS and Jade/PUG so I know this can be a really simple question for many of you but for me I can't understeand any of the answers a get from the internet because the term 'Promise' and how it works is a little "confusing" for me.
I am querying a postgre database to get several values from a table (really simple query). If I do this without using a promise everything works fine, console prints the result and everyone is happy, but when I try to store this result into a variable and pass it as a parameter to a Jade template things change.
I have read that, in order to do that, I need to use promises because it is more likely that when the variable is being accessed, the value might not be resolved yet and that's what Promises are for. So here I have my code:
hero.js:
getHeroes: function()
{
//Initialize array
var elem = [];
console.log('Querying heroes');
return new Promise((resolve, reject) =>
{
pg.connect(conString, function (err, client, done)
{
if (err)
{
return console.error('error fetching client from pool', err)
}
//Execute SELECT query
client.query('SELECT name from heroe;', function (err, rows, result)
{
//Iterate over results
for (var i = 0; i < rows.rowCount; i++)
{
//PUSH result into arrays
elem.push(rows.rows[i].name);
}
done()
if (err)
{
return console.error('error happened during query', err)
}
resolve(elem)
})
});
})
}
And this the part of my server.js where I call that function:
app.get('/jade', (request, response) => {
var heroList = [];
heroList = hero.getHeroes();
console.log(heroList);
response.render('test_jade', {param1: 'test'});
})
that console.log shows up "Promise { pending }" and I don't know how to "listen to the resolved event and retrieve the value from it once it has finished".
Would appreciate any advice/solution or even a good Node.js manual where all this mess is explained for total newbies like me.
Thanks in advance!
It's not how you use promise.
Try this,
app.get('/jade', (request, response) => {
var heroList = [];
hero.getHeroes().then(data=>{
heroList = data;
console.log(heroList);
response.render('test_jade', {param1: 'test'});
}).catch(e=>{
//handle error case here when your promise fails
console.log(e)
})
})
You should also catch in case your promise fails.
Related
I am trying to build an API through which I can get whois detail in the JSON output like below
For this, I installed the whois package from npm (https://www.npmjs.com/package/whois[whois Link]2). I tried to convert the string to object and print it in JSON format but I am not getting any output on the web but it console i can get the data easily. Can you guys please fix my error.
function whoisFunction() {
var whois = require('whois')
whois.lookup(url,async function(err, data) {
try {
a = await data.split('\n')
}
catch (e) {
console.log(e)
return e
}
c=[]
for(i = 0; i < a.length; i++){
c.push(a[i])
}
await console.log(typeof (c))
console.log(c)
return a
})
}
// res.json({'Headers':+whoisFunction()})
res.status(200).json(whoisFunction())
There are async and awaits sprinkled seemingly randomly all over your function.
You should realize that the only thing that is asynchronous here is whois.lookup().
console.log is not asynchronous. Array.prototype.split is not asynchronous. The callback (err, data) => {...} is not asynchronous.
If you want to use the callback pattern, then you need to use res.send() inside of the callback
(err, data) => {
res.send(data)
}
But we got fed up with callback-patterns because of how messy it is to nest them. So instead we went over to using promises. If you have callbacks but want use promises, then you wrap the callback in a promise. You do it once, and you do it as tight to the offending callback as you can:
function promisifiedLookup(url){
return new Promise( (resolve, reject) => {
whois.lookup(url, function(err, data) {
if(err) reject(err)
else resolve(data)
})
})
}
So, to use async/await we need that:
the calling function is declared async
the called function is returning a promise (or else there is nothing to wait for)
async function whoisFunction() {
let data = await promisifiedLookup(url) // _NOW_ we can use await
data = data.split('\n')
// ...
return data; // Because this funtion is declared async, it will automatically return promise.
}
If your express-handler is defined as async, then you now can use await here as well:
res.status(200).json(await whoisFunction())
I made a "promisified" version of fs.readFile using Javascript's native Promise, which parses a JSON file and returns the object when resolving.
function readFileAsync(file, options) {
return new Promise(function (resolve, reject) {
fs.readFile(file, options, function(err, data) {
if (err) {
reject(err);
} else {
var object = JSON.parse(data);
resolve(object);
}
});
});
}
I first tried chaining the promises myself, but for some reason first the districts.variable1 from promise 1 gets logged, then comparePersonalities is called in promise 3, which gives an error because user is still undefined, and then user.variable2 gets logged from promise 2.
var user, district;
readFileAsync(file1, 'utf8').then(function (data) {
districts = data;
console.log(districts.variable1);
}).then(readFileAsync(file2, 'utf8').then(function (data) {
user = data;
console.log(user.variable2);
})).then(function (result) {
comparePersonalities(districts, user);
}).catch(function (e) {
console.log(e);
});
I also tried an alternate approach using Promise.all but this still results in an incorrect ordering and the comparePersonalities failing.
Promise.all([readFileAsync(file1), readFileAsync(file2)]).then(function (first, second) {
districts = first;
user = second;
comparePersonalities(districts, user);
});
When logging the resolved objects within the promise everything seems to work well, I can't figure out why everything is eventually initialized but the last promise runs before the second promise has finished. What am I doing wrong in both the chained promise and in Promise.all?
Promise.all is much more appropriate for your use case. You made a mistake in the callback: the outer promise is resolved with an array of results (in the same order as the inner promises), so then(function (first, second) {...}) is incorrect. Try something like this
Promise.all([
readFileAsync(file1, 'utf8'),
readFileAsync(file2, 'utf8')
]).then(function (results) {
// note that "results" is an array of two values
var districts = results[0];
var user = results[1];
comparePersonalities(districts, user);
});
Promises are always resolved with only one value. That's really important, and actually simplifies a lot of things, since you always know how many elements to expect.
The first example
You've made a mistake, in which you are passing a Promise to the .then method, when in reality it always expects a function. Notice the snippet readFileAsync(file2, 'utf8') is nicely wrapped in an anonymous function.
var user, district;
readFileAsync(file1, 'utf8').then(function (data) {
districts = data;
console.log(districts.variable1);
})
.then(function () { return readFileAsync(file2, 'utf8') })
.then(function (data) {
user = data;
console.log(user.variable2);
}).then(function (result) {
comparePersonalities(districts, user);
}).catch(function (e) {
console.log(e);
});
The second example
But, in that case you're probably better off using the Promise.all method, as the promises get resolved and nicely returned in your next funciton call.
The problem in your snippet is that promises always resolve one single object, and in the case of Promise.all, you should be expecting one single array. You can actually use es6 destructuring to simplify your code:
Promise.all([readFileAsync(file1), readFileAsync(file2)])
.then(function ([districts, user]) {
comparePersonalities(districts, user);
});
You have to return the Promises each time for chaining:
readFileAsync(file1, 'utf8').then(function(data) {
districts = data;
console.log(districts.variable1);
return readFileAsync(file2, 'utf8');
}).then(function(data) {
user = data;
console.log(user.variable2);
comparePersonalities(districts, user);
}).catch(function(e) {
console.log(e);
});
comparePersonalities(districts, user) will only work if your variable districts is declared at a higher scope. Otherwise it will be undefined once you reach this function.
I've spent so much time trying to find the answer to this on here and come up with nothing. Hoping someone can enlighten me..
I have code which is making an async call to a database and returning data in a callback function (in my case I'm using MongoClient, which returns a Promise). However, I can't work out how to use the resulting data to actually set function-level variables - whenever I try to do it the resulting value that I log is either undefined or a pending Promise object.
There's lots of posts on this subject but I can't find any methods that work when I try to apply them. Any and all help gratefully received!
function lookupOneDbEntry(key, value) {
var responseData = "initial data"
// search for the entry in the database
db.collection("my_collection").findOne({key: value}, function(err, result) {
if (err) {
//if database throws an error
responseData = "db error";
}
else {
// if the entry is found, return the data
responseData = result;
}
});
return responseData;
}
EDIT: I am aware of other posts on this (like this one here and, while exhaustive documentation is useful to an extent, I;m having trouble using this information practically in a real-life implementation like the one above. Hence my question here.
Async calls happens outside of the call stack that you are on. you can't return it onto the current stack.
So we use the promises to hook into the results of our call.
function lookupOneDbEntry(key, value) {
return new Promise(function (resolve, reject) {
// search for the entry in the database
db.collection("my_collection").findOne({key: value}, function(err, result) {
if (err) {
//if database throws an error
reject(err);
}
else {
// if the entry is found, return the data
resolve(result);
}
});
});
}
lockupOneDbEntry('myKey', 'myValue').then(function (result) {
console.log('result', result);
}, function (err) {
console.log("error!", err);
});
After a long while of experimenting I've finally managed to do it - I didn't need any fancy callbacks or additional Promises in the end, I just removed the optional callback in the database request and instead processed the returned promise separately.
function lookupOneDbEntry(key, value) {
var responseData = "initial data";
var solution = db.collection("accounting_module").findOne({key: value});
solution.then(function (result) {
// validation of result here
responseData = result;
});
return responseData;
}
I want to change the Tags format which I am fetching form one of the collections. Tags data contains some KC ids in an array which I am using to get KC data and insert in TagUnit to get final response format.
var newTags = Tags.map(function(TagUnit) {
for (var i = 0; i < TagUnit.kcs.length; i++) {
KCArray = [];
KC.findById(TagUnit.kcs[i], function(error, data) {
KCMap = {};
KCMap['kc_id'] = data._id;
KCMap['kc_title'] = data.title;
KCArray.push(KCMap);
if (KCArray.length == TagUnit.kcs.length) {
TagUnit.kcs = KCArray;
}
});
}
return TagUnit;
});
response.send(JSON.stringify(newTags));
But I am not getting desired result. Response is giving out Tag data in initial for instead of formatted form. I guess it is due to event looping. I will be grateful if someone can help me with this.
**Edit: ** I am using MongoDB as database and mongoose as ORM.
I'd suggest using promises to manage your async operations which is now the standard in ES6. You don't say what database you're using (it may already have a promise-based interface). If it doesn't, then we manually promisify KC.findById():
function findById(key) {
return new Promise(function(resolve, reject) {
KC.findById(key, function(err, data) {
if (err) return reject(err);
resolve(data);
});
});
}
Then, assuming you can do all these find operations in parallel, you can use Promise.all() to both keep track of when they are all done and to order them for you.
var allPromises = Tags.map(function(TagUnit) {
var promises = TagUnit.kcs.map(function(key) {
return findById(key).then(function(data) {
// make resolved value be this object
return {kc_id: data._id, kc_title: data.title};
});
});
// this returns a promise that resolves with an array of the kc_id and kc_title objects
return Promise.all(promises).then(function(results) {
return {
_id: TagUnit._id,
kcs: results
};
});
});
// now see when they are all done
Promise.all(allPromises).then(function(results) {
response.send(JSON.stringify(results));
}).catch(function(err) {
// send error response here
});
You can use promises or Async module
var async = require('async');
...
function getKC (kc, callback) {
KC.findById(kc, function(err, data) {
if (err)
return callback(err);
callback(null, {kc_id: data._id, kc_title: data.title})
});
}
function getKCs (tag, callback) {
async.map(tag.kcs, getKC, callback);
}
async.map(Tags, getKCs, function(err, results){
if (err)
return console.log(err.message);
res.json(results); // or modify and send
});
P.S. Perhaps, code contains errors. I cann't test it.
I'm building a twitter clone using Node.js and MongoDB with mongoose. My Tweet model has body, user and created fields where user is the id of the user who has created the tweet. Now I'm building the API. I want when I make a GET request to receive a list of all the tweets (/api/tweets/) but except the user field (which returns only the id of the user) I want to get the whole user object so that I can display information about the tweet owner in my front-end part. I ended up with the following code.
exports.all = function (req, res, next) {
Tweet.find({}, function (err, tweets) {
if (err) return res.json(400, err);
var response = [];
tweets.forEach(function (element, index, array) {
var tweet = {};
tweet._id = element._id;
tweet.created = element.created;
tweet.body = element.body;
User.findById(element.user, function (err, user) { // <- This line
if (err) return res.json(400, err);
tweet.user = user;
});
response.push(tweet);
});
return res.json(response);
});
};
It works perfectly except that it doesn't add the user info. The problem is in the line I have marked. When javascript comes to that line, it tries to make it "parallel" and continues with the code without executing the callback function. But then it pushes the tweet object that doesn't have yet user info. How can I fix this?
You're going to want to use the async library. It will make your life much easier.
// inside `Tweet.find`
async.each(tweets, function(done) {
// do stuff to tweets
User.findById(element.user, function(err, user){
if (err) return done(err);
// do stuff with user
done();
});
}, function(err) {
// called when done
res.json(response);
});
The issue is that res.json sends the response so it doesn't matter that findById is being called. You need to call res.json once everything is done.
You can do this in several ways, but the easiest one with your existing code is to just keep a counter:
var tweetCount = 0;
tweets.forEach(/* snip */) {
User.findById(element.user, function (err, user) {
tweet.user = user;
tweetCount++;
response.push(tweet);
if (tweetCount == tweets.length) {
res.json(response);
}
});
});
You can use Q promise library to sync. It is simple and easy to use.
Response will only be send when whole promise chain is compeleted
var result = Q();
tweets.forEach(function (tweet, i) {
result = result.then(function () {
// do your stuff
}
result.then(function () {
res.json(response); //successfully completed
});