vendredi 27 février 2015

How to reject (and properly use) Promises?

Short story:




  • Talking about Promises/A+, what is the proper way to reject a promise - throwing an error? But if I miss the catch - my whole app will blow!




  • How to use promisify and what are the benefits of it (maybe you'll need to read the longer version)?




  • Is .then(success, fail) really an anti-pattern and should I always use .then(success).catch(error)?




Longer version, described as real life problem (would love someone to read):


I have a library that uses Bluebird (A+ Promise implementation library), to fetch data from database (talking about Sequelize). Every query returns a result, but sometimes it's empty (tried to select something, but there wasn't any). The promise goes into the result function, because there is no reason for an error (not having results is not an error). Example:



Entity.find(1).then(function(result) {
// always ending here, despite result is {}
}).catch(function(error) {
// never ends here
});


I want to wrap this and check if the result is empty (can't check this everywhere in my code). I did this:



function findFirst() {
return Entity.find(1).then(function(result) {
if (result) { // add proper checks if needed
return result; // will invoke the success function
} else {
// I WANT TO INVOKE THE ERROR, HOW?! :)
}
}).catch(function(error) {
// never ends here
});
}

findFirst().then(function(result) {
// I HAVE a result
}).catch(function(error) {
// ERROR function - there's either sql error OR there is no result!
});


If you are still with me - I hope you will understand what's up. I want somehow to fire up the error function (where "ERROR function" is). The thing is - I don't know how. I've tried these things:



this.reject('reason'); // doesn't work, this is not a promise, Sequelize related
return new Error('reason'); // calls success function, with error argument
return false; // calls success function with false argument
throw new Error('reason'); // works, but if .catch is missing => BLOW!


As you can see by my comments (and per spec), throwing an error works well. But, there's a big but - if I miss the .catch statement, my whole app blows up.


Why I don't want this? Let's say I want to increment a counter in my database. I don't care about the result - I just make HTTP request.. So I can call incrementInDB(), which has the ability to return results (even for test reasons), so there is throw new Error if it failed. But since I don't care for response, sometimes I won't add .catch statement, right? But now - if I don't (on purpose or by fault) - I end up with your node app down.


I don't find this very nice. Is there any better way to work it out, or I just have to deal with it?


A friend of mine helped me out and I used a new promise to fix things up, like this:



function findFirst() {
var deferred = new Promise.pending(); // doesnt' matter if it's Bluebird or Q, just defer
Entity.find(1).then(function(result) {
if (result) { // add proper checks if needed
deferred.resolve(result);
} else {
deferred.reject('no result');
}
}).catch(function(error) {
deferred.reject('mysql error');
);

return deferred.promise; // return a promise, no matter of framework
}


Works like a charm! But I got into this: Promise Anti Patterns - wiki article written by Petka Antonov, creator of Bluebird (A+ implementation). It's explicitly said that this is wrong.


So my second question is - is it so? If yes - why? And what's the best way?


Thanks a lot for reading this, I hope someone will spent time to answer it for me :) I should add that I didn't want to depend too much on frameworks, so Sequelize and Bluebird are just things that I ended up working with. My problem is with Promises as a global, not with this particular frameworks.


Aucun commentaire:

Enregistrer un commentaire