I am currently trying to refactor the codebase that I have and want to have a more developer friendly codebase. Part 1 of that is changing callback to Promises. Currently, at some places we are using Async.waterfall to flatten the callback hell, which works for me. The remaining places where we couldn't was because they were conditional callbacks, That means different callback function inside if and else
if(x){
call_this_callback()
}else{
call_other_callback()
}
Now I use bluebird for promises in Node.JS and I can't figure out how to handle conditional callback to flatten the callback hell.
EDIT
A more realistic scenario, considering I didn't get the crux of the issue.
var promise = Collection1.find({
condn: true
}).exec()
promise.then(function(val) {
if(val){
return gotoStep2();
}else{
return createItem();
}
})
.then(function (res){
//I don't know which response I am getting Is it the promise of gotoStep2
//or from the createItem because in both the different database is going
//to be called. How do I handle this
})
There is no magic, you can easily chain promises with return inside a promise.
var promise = Collection1.find({
condn: true
}).exec();
//first approach
promise.then(function(val) {
if(val){
return gotoStep2()
.then(function(result) {
//handle result from gotoStep2() here
});
}else{
return createItem()
.then(function(result) {
//handle result from createItem() here
});
}
});
//second approach
promise.then(function(val) {
return new Promise(function() {
if(val){
return gotoStep2()
} else {
return createItem();
}
}).then(function(result) {
if (val) {
//this is result from gotoStep2();
} else {
//this is result from createItem();
}
});
});
//third approach
promise.then(function(val) {
if(val){
return gotoStep2(); //assume return array
} else {
return createItem(); //assume return object
}
}).then(function(result) {
//validate the result if it has own status or type
if (Array.isArray(result)) {
//returned from gotoStep2()
} else {
//returned from createItem()
}
//you can have other validation or status checking based on your results
});
Edit: Update sample code because author updated his sample code.
Edit: Added 3rd approach to help you understand promise chain
Here is the answer to the promise branch: nested and unnested.
Make the branches and don't join them together.
Related
I'm learning NodeJS and I have the following code:
var test = '';
function test2(name,callback) {
UserData
.findOne({'token': name})
.then(function(user) {
test = user.token;
console.log('current: '+user.token);
return callback(user.token);
})
.catch(function(err) {
console.log(err);
});
}
var isAuthenticated = function(req,res,next){
test2(req.cookies.remember_me, function(user) {test=user; });
console.log('test:::: '+test);
var isLog = false;
if(req.session.user!= undefined && req.session.user===test){
isLog=true;
}
if(req.cookies.remember_me ===test){
console.log('test'+test);
isLog=true;
}
if(isLog){
return 1;
}else
{
console.log('not auth');
return -1;
}
}
and the result is :
test:::: P9Ysq2oSCHy1RVyWsePxJhpEYLD81qOiIayTyiNJCnOkmllvEspwrDAW8tD9rmfJ
not auth
current: k8LJcCty6568QpXNS3urBedlJ0MDfEYlbOqo9Q7tQi9EOyeSkyesgHHzUjBhDgZx
I know it's bcause if the async nature of NodeJS but how can i make test to be always the same as 'current';
Thank you.
You are making a pretty classic mistake of expecting code to run in the order written, but it doesn't because of the async nature of javascript. For example:
test2(req.cookies.remember_me, function(user) {test=user; });
console.log('test:::: '+test);
Here your console.log() will run before the callback because the callback only happens after you've heard back from the DB. (Although it's not clear where that test value ('P9Ysq2oSCH...') is coming from.
As long as you're learning Node, you should start by trying to avoiding mixing callbacks and promises. Your findOne() function returns a promise, which is why you can call then() on it. You should just return this promise and then call then() in the calling function:
function test2(name) {
// return is important - you're returning the promise which you will use later.
return UserData.findOne({'token': name})
.then(function(user) {
return user.token;
})
.catch(function(err) {
console.log(err);
});
}
function isAuthenticated(req,res,next){
return test2(req.cookies.remember_me)
.then(function(token) {
console.log('test:::: '+token);
var isLog = false;
if(req.session.user!= undefined && req.session.user===token){
isLog=true;
}
if(req.cookies.remember_me ===token){
isLog=true;
}
return isLog
})
}
// Use the function
isAuthenticated(req,res,next)
.then(function(isLoggedin){
if(isLoggedin) {
// user logged in
} else {
// not logged in
}
})
it's possible save result query of rethinkdb in a variable?
Like this??
var test = r.db('chat').table('group_chat').count(r.row('passengers').contains(function(passeggers) {
return passeggers('nome').eq('pigi');
})).run()
now I use this method
var test;
r.db('chat').table('group_chat').count(r.row('passengers').contains(function(passeggers) {
return passeggers('nome').eq('pigi');
})).run().then(function(response){
test = response;
})
If you don't like using promises and are using the latest version of ECMAScript, you can use async/await, which provides syntactic sugar to let you write your asynchronous code as if it were synchronous.
You could rewrite your example like this:
async function getTest() {
var test = await r.db('chat').table('group_chat').count(r.row('passengers').contains(function(passeggers) {
return passeggers('nome').eq('pigi');
})).run();
return test;
}
Note that “behind the scenes”, this still uses promises, but it makes the code easier to read and understand.
Simplest yet working solution would be as bellow, using callback
function getTest(callback) { // add callback
r.db('chat')
.table('group_chat')
.count(r.row('passengers')) // you forgot closing brace here
.contains(function(passeggers) {
return passeggers('nome').eq('pigi')
})
.run()
.then(function(result) {
callback(null, result); // call callback with result
})
.error(function(err) {
callback(err, null); // call callback with error
});
}
getTest(function(err, result) { // invoke callback
if (err) throw err;
console.log(result)
var test = result; // note: you can use test variable only inside this call
})
I am trying to get Promise chaining working for me correctly.
I believe the problem boils down to understanding the difference between:
promise.then(foo).then(bar);
and:
promise.then(foo.then(bar));
In this situation I am writing both foo and bar and am trying to get the signatures right. bar does take a return value that is produced by foo.
I have the latter working, but my question is what do I need to do to get the former working?
Related to the above is the full code (below). I don't have the different logs printed in the order I am expecting (expecting log1, log2, log3, log4, log5, but getting log3, log4, log5, log1, log2). I am hoping as I figure the above I will get this working right as well.
var Promise = require('bluebird');
function listPages(queryUrl) {
var promise = Promise.resolve();
promise = promise
.then(parseFeed(queryUrl)
.then(function (items) {
items.forEach(function (item) {
promise = promise.then(processItem(transform(item)))
.then(function() { console.log('log1');})
.then(function() { console.log('log2');});
});
}).then(function() {console.log('log3')})
).then(function() {console.log('log4')})
.catch(function (error) {
console.log('error: ', error, error.stack);
});
return promise.then(function() {console.log('log5');});
};
What is the difference between promise.then(foo).then(bar); and promise.then(foo.then(bar));?
The second one is simply wrong. The then method takes a callback as its argument, not a promise. That callback might return a promise, so the first one is equivalent to
promise.then(function(x) { return foo(x).then(bar) })
(assuming that foo returns a promise as well).
Your whole code appears to be messed up a bit. It should probably read
function listPages(queryUrl) {
return parseFeed(queryUrl)
.then(function (items) {
var promise = Promise.resolve();
items.forEach(function (item) {
promise = promise.then(function() {
console.log('log1');
return processItem(transform(item));
}).then(function() {
console.log('log2');
});
});
return promise;
}).then(function() {
console.log('log3')
}, function (error) {
console.log('error: ', error, error.stack);
});
}
I have a method (see code example) and I'm trying to return a promises list to parent method. My problem is when pushing promises in my promiseList array.
getDistance is a method returning a Promise. If I do .then() in getDistance method,everything is OK. Nevertheless, if I try to push the promise into my array, when I do .then in parent method, it is empty.
I think it may happen due to each loop is asynchronous...
var promiseList = [];
res = db.collection.find query statement...
res.each(function(err, doc) {
if (doc!==null) {
promiseList.push(
getDistance(orgLat, orgLon, destLat, desLon)
);
}
});
return Promise.all(promTimeList);
I'm using MongoDB as database, and NodeJS in server side, with mongodb as driver to connecto to my database and Bluebird as library to implement promises.
Thanks in advance!
I think you are correct in that the issue is due to the fact that each is asynchronous. You can use defer to wrap callback APIs (like each) into promises. It would work something like this:
res = db.collection.find query statement...
processResponse(res)
.then(function(promiseList) {
// execute all the promises
return Promise.all(promiseList);
}, function (err) {
// handle error
});
function processResponse(res) {
var deferred = Promise.pending();
var promiseList = [];
res.each(function(err, doc) {
if (err) {
// error, abort
deferred.reject(err);
return;
}
else if (doc != null) {
promiseList.push(
getDistance(orgLat, orgLon, destLat, desLon)
);
}
else {
// finished looping through all results, return the promise list
deferred.resolve(promiseList);
}
});
return deferred.promise;
}
See more on defer with bluebird at the following link (look for "So when should deferred be used?"):
https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns
Update: According to this post:
Define empty Bluebird promise like in Q
It looks like there is a Promise constructor that may be the preferred way to do this. The processResponse method would look like this:
function processResponse(res) {
return new Promise(function(resolve, reject) {
var promiseList = [];
res.each(function(err, doc) {
if (err) {
// error, abort
reject(err);
return;
}
else if (doc != null) {
promiseList.push(
getDistance(orgLat, orgLon, destLat, desLon)
);
}
else {
// finished looping through all results, return the promise list
resolve(promiseList);
}
});
});
}
Thanks to #Bergi. Please correct me if I am wrong. I am more familiar with the Q library (https://github.com/kriskowal/q) than bluebird.
Iterate the collection syncrhonously by using await with cursor.next() in a while loop:
var cursor = db.collection.find({});
while ((doc = await cursor.next()) != null) {
promiseList.push(
getDistance(doc.orgLat, doc.orgLon, doc.destLat, doc.desLon);
);
}
I'm trying to using the Mongoose Promises to have a cleaner code (see nested functions).
Specifically, I'm trying to build something like this:
Model.findOne({_id: req.params.id, client: req.credentials.clientId}).exec()
.then(function(resource){
if (!resource) {
throw new restify.ResourceNotFoundError();
}
return resource;
})
.then(function(resource) {
resource.name = req.body.name;
return resource.save; <-- not correct!
})
.then(null, function(err) {
//handle errors here
});
So, in one of the promises I would need to save my model. As of the latest stable release, Model.save() does not return a promise (bug is here).
To use the classical save method, I could use this:
//..before as above
.then(function(resource) {
resource.name = req.body.name;
resource.save(function(err) {
if (err)
throw new Error();
//how do I return OK to the parent promise?
});
})
But as also commented in the code, how do I return to the holding promise the return value of the save callback (which runs async)?
Is there a better way?
(btw, findOneAndUpdate is a no-go solution for my case)
One way of doing it would be to wrap the .save code in your own method which returns a promise. You'll need a promise library, like RSVP or Q. I'll write it in RSVP, but you should get the idea.
var save = function(resource) {
return new RSVP.Promise(function(resolve, reject) {
resource.save(function(err, resource) {
if (err) return reject(err);
resolve(resource);
});
});
}
Then in your calling code:
// ...
.then(function(resource) {
resource.name = req.body.name;
return save(resource);
})
.then(function(resource) {
// Whatever
})
.catch(function(err) {
// handle errors here
});
The other way of doing it would be to nodeify the save method, but I'd do it they way I've detailed above.