Alternate implementation for nodejs+mysql+Q solution? - javascript

I am trying to avoid the pyramid of doom. I have a REST function on my server that returns a "payload" object with three JSON fields for the results. Each of the three fields is calculated with a mysql query call. I figured out the nodejs way of putting the next query in the callback function of the current query, but pyramid of doom beckons!
I found this solution using Q promises which is fantastic, but I cannot figure out from the Q docs how the deferred and .makeNodeResolver work. Also the results object is intimidating and I had to find my results through trial and error... is there an alternative way to solve this problem? Am I making it harder than it has to be?
Stack Overflow answer with helpful but confusing Q syntax (programath's answer)
My code:
var queryHelper = function(request, response, payload)
{
var queryQB = queryConstructionHelper(request, '"QB"')
var queryRB = queryConstructionHelper(request, '"RB"')
var queryWRTE = queryConstructionHelper(request, '"WRTE"')
function doQuery1()
{
var deferred = Q.defer();
database.query (queryQB, deferred.makeNodeResolver());
return deferred.promise;
}
function doQuery2()
{
var deferred = Q.defer();
database.query (queryRB, deferred.makeNodeResolver());
return deferred.promise;
}
function doQuery3()
{
var deferred = Q.defer();
database.query (queryWRTE, deferred.makeNodeResolver());
return deferred.promise;
}
Q.all([doQuery1(),doQuery2(),doQuery3()]).then(function(results)
{
payLoadAssignHelper('"QB"', payload, results[0][0][0].payloadCount);
payLoadAssignHelper('"RB"', payload, results[1][0][0].payloadCount);
payLoadAssignHelper('"WRTE"', payload, results[2][0][0].payloadCount);
response.send(payload);
});
}

Related

NodeJs forEach request-promise wait for all promises before returning

Problem is I'm not able to get the promises to return anything. they.. just come empty.
Every answer I see here on SO is telling me to do just this, though for some reason this is not working. I'm at my wits end, pulling hair and smashing keyboards; Can someone pin-point my dumbness?
var q = require('q');
var request = require('request-promise'); // https://www.npmjs.com/package/request-promise
function findSynonym(searchList) {
var defer = q.defer();
var promises = [];
var url = "http://thesaurus.altervista.org/service.php?word=%word%&language=en_US&output=json&key=awesomekeyisawesome";
var wURL;
searchList.forEach(function(word){
wURL = url.replace('%word%',word);
promises.push(request(wURL));
});
q.all(promises).then(function(data){
console.log('after all->', data); // data is empty
defer.resolve();
});
return defer;
}
var search = ['cookie', 'performance', 'danger'];
findSynonym(search).then(function(supposedDataFromAllPromises) { // TypeError: undefined is not a function [then is not a function]
console.log('->',supposedDataFromAllPromises); // this never happens
});
You're returning the Deferred object defer, which does not have a .then method, instead of the Promise object defer.promise.
But anyway, that's the deferred antipattern, there's no need of using deferreds here. Just return the promise that Promise.all gets you:
function findSynonym(searchList) {
var url = "http://thesaurus.altervista.org/service.php?word=%word%&language=en_US&output=json&key=awesomekeyisawesome";
var promises = searchList.map(function(word) {
return request(url.replace('%word%', word));
});
return q.all(promises).then(function(data){
console.log('after all->', data); // data is empty
return undefined; // that's what you were resolve()ing with
});
}
So, turns out I was resolving the promises or something something. returning the q.all() worked pretty well :)
function findSynonym(searchList) {
var promises = [];
var url = "http://thesaurus.altervista.org/service.php?word=%word%&language=en_US&output=json&key=REDACTED";
var wURL;
searchList.forEach(function(word){
wURL = url.replace('%word%',word);
promises.push(request({url:wURL}));
});
return q.all(promises);
}
var search = ['cookie', 'performance', 'danger'];
findSynonym(search)
.then(function(a){
console.log('->',a);
});

Node.js Q Promises Multiple Parameters

Trying to cleanup my callback spaghetti code using the Q promise library in my nodejs express app, but I'm having trouble translating some parts of it. Having trouble passing multiple arguments to functions and dealing with the scope.
Here's a simplified "synchronous" version to show my logic:
function updateFacebook(req, res) {
var user = getUserFromDB(userid);
var profile = getUserProfileFromAPI(accessToken);
var success = updateUserDB(user, profile);
res.json({ result: success });
}
So I convert the callback functions to return promises
function getUserFromDB(userid) {
var deferred = Q.defer();
// somewhere in here there's a deferred.resolve(user object);
queryMongo()...
return deferred.promise;
}
function getUserProfileFromAPI(accessToken) {
var deferred = Q.defer();
// somewhere in here there's a deferred.resolve(profile object);
request()...
return deferred.promise;
}
function updateUserDB(user, profile) {
var deferred = Q.defer();
// somewhere in here there's a deferred.resolve(updated user object);
updateMongo()...
return deferred.promise;
}
function handleResponse(res, user) {
var deferred = Q.defer();
// was thinking about putting the res.json here
// i have no way of passing in the res
// and res is out of scope...
res.json({});
return deferred.promise;
}
Now the problem is linking them up, I tried...
Q.when(getUserFromDB(userid), getUserProfileFromAPI(accessToken))
.spread(updateUserDB)
.done(handleResponse);
Q.all([getUserFromDB(userid), getUserProfileFromAPI(accessToken)])
.spread(updateUserDB)
.done(handleResponse);
Super confused. Any direction would be much appreciated.
Looks like your handleResponse is expecting two params, but updateUserDB is only resolving a single object. You could do something like:
function getResponseHandler(res) {
return function(user) {
// your handleResponse code here
// which now has access to res
}
}
and then call it like:
Q.all([getUserFromDB(userid), getUserProfileFromAPI(accessToken)])
.spread(updateUserDB)
.done(getResponseHandler(res));

Node js not resolving array of promises

I'm trying to execute several async requests and trying to get the output using promises.
If I've multiple requests queued up the Q.all(promises).then () function doesn't seem to be working. For a single request the promises are all resolved. The sample code is here.
var request = require('request');
var Q = require('q');
var sites = ['http://www.google.com', 'http://www.example.com', 'http://www.yahoo.com'];
// var sites = ['http://www.google.com']
var promises = [];
for (site in sites) {
var deferred = Q.defer();
promises.push(deferred.promise);
options = {url: sites[site]};
request(options, function (error, msg, body) {
if (error) {
deferred.reject();
}
deferred.resolve();
});
}
Q.all(promises).then (function () {
console.log('All Done');
});
What am I doing wrong here ?
Surya
Here is what I'd do in the scenario, this is the whole code:
var Q = require('q');
var request = Q.nfbind(require('request'));
var sites = ['http://www.google.com', 'http://www.example.com', 'http://www.yahoo.com'];
var requests = sites.map(request);
Q.all(requests).then(function(results){
console.log("All done") // you can access the results of the requests here
});
Now for the why:
Always promisify at the lowest level possible, promisify the request itself and not the speicific requests. Prefer automatic promisification to manual promisification to not make silly errors.
When working on collections - using .map is easier than manually iterating them since we're producing exactly one action per URL here.
This solution is also short and requires minimal nesting.
Don't use for..in to iterate over arrays. What this actually does is set site to 0, 1, 2 and this just doesn't work very well. Use some other form of iteration like a regular for loop, or Array.prototype.forEach
sites.forEach(function (site) {
var deferred = Q.defer();
promises.push(deferred.promise);
options = {url: site};
The problem is that you're changing the value of deferred on every tick of your for loop. So, the only promise which is actually resolved in your example is the last one.
To fix it you should store the value of deferred in some context. The easiest way to do so is to use Array.prototype.forEach() method instead of for loop:
sites.forEach(function (site){
var deferred = Q.defer();
var options = {url: sites[site]};
promises.push(deferred.promise);
request(options, function (error, msg, body) {
if (error) {
deferred.reject();
}
deferred.resolve();
});
})
And you also missed var declaring options variable. In JavaScript it means declaration of a global variable (or module-wide variable in node.js).

Cache server response (AngularJS, deferred)

I'm trying to create an angular js service that will fetch data from the server if update needed or return cached array if there is no update. In both cases service should return a promise. Service code:
getLikesForMe = function() {
var defer = $q.defer(), prom = defer.promise;
if (angular.isUndefined(this.likesForMe) ||
updateStatus.likesForMe === true) {
var that = this;
prom = $http.get(API_URL + 'likes-to.json')
.then(function(result){
updateStatus.likesForMe = false;
that.likesForMe = result.data;
});
} else {
defer.resolve(this.likesForMe);
}
return prom;
}
Current controller code:
MainUser.getLikesForMe().then(function(result) {
$scope.likesList = result;
});
Prefered controller code:
$scope.likesList = MainUser.getLikesForMe();
But currently it only works after second function (getLikesForMe()) call, on the first - the list is empty and the "result" variable is undefined.
I'm new to deferred objects and my english is pretty poor, but I hope you understand where the mistake is. Thanks a lot!
You have 2 issues
On the first call, the promise you return will be resolved with undefined as you're not returning anything from the then success callback from your call to $http.get. If you return that.likesForMe from that callback, I suspect it will work as you expect. You should probably read up on chaining promises. (Shameless plug: I wrote a blog post on AngularJS promises which contains sections on chaining)
The code is quite complicated for what it does, and you almost-never have to create a promise via $q.defer() if all you're doing to working with existing promises. It usually makes things more complicated and harder to deal with errors. You can also use $q.when() to create a promise from a non-promise value. So I propose something like
getLikesForMe = function() {
var that = this;
var useCache = !updateStatus.likesForMe && !angular.isUndefined(this.likesForMe);
return useCache ? $q.when(that.likesForMe) : $http({
method: 'GET',
url: API_URL + 'likes-to.json'
}).then(function(results) {
updateStatus.likesForMe = false;
that.likesForMe = results.data;
return that.likesForMe;
});
});
You could also read up in the docs for $http cache to see if you could come up with a solution that uses it.
If you prefer the later version of the controller code then you can't return a promise from the service.
You could return an empty object that would be augmented when you got back from the http call.
You'd then have to issue a $rootScope.$apply() so your controller is notified.

Using $.Deferred() as a callback

in my project I'm not using callbacks instead I'm trying to use $.Deferred to have uniform logic across all application, I have lots places in my code where I do something like the following:
function someMagicHandler(request) {
var me = this;
var sandbox = me.getSandbox();
var options = request.options;
var deferred = request.deferred;
var result = [];
var databaseOperation = sandbox.database.all('records').done(function (records) {
result.concat(records);
deferred.notify(records);
});
var serverResponse;
var serverOperation = sandbox.server.requestRecords(options).then(function (response) {
// First we are trying to save received records to database
serverResponse = response;
result.concat(response.Records);
deferred.notify(response.Records);
return sandbox.database.put('records', response.Records);
}).done(function() {
sandbox.storage.setTimestamp('records', new Date(serverResponse.Timestamp));
});
$.when(databaseOperation, serverOperation).then(deferred.resolve, deferred.reject);
}
In this code I'm personally don't like one of the last lines:
$.when(databaseOperation, serverOperation).then(deferred.resolve, deferred.reject);
Is there a standard way to express:
$.when(databaseOperation, serverOperation).then(deferred);
which would essentially mean:
$.when(databaseOperation, serverOperation).then(deferred.resolve, deferred.reject, deferred.notify);
Edit
I've investigated this problem deeper, and it seems that below solution is ok only when you are not relying on deferred.progress() which has no memory and therefore will not return any data in case when subsequent async operation is complete synchronously.
Summary
If you are using $.Deferred() as a callback (i.e. when you rely on notify or progress functions and in that case you need pass it as an argument) than you will be obligated to use the ugly
blahblahblah.then(deferred.resolve, deferred.reject, deferred.notify)
You can just replace this
$.when(databaseOperation, serverOperation).then(deferred.resolve, deferred.reject);
with this:
request.deferred = $.when(databaseOperation, serverOperation);
And delete all references to the variable deferred because $.when already creates a promise for you with (as far as I read the manual).

Categories

Resources