Calling then before or after Promise.all - javascript

Currently I have following code:
var detailPromises = links.map(function (link) { return requestP(link) });
var oldPollNames = [];
// add a database call to the promises as it also can resolved during detail fetching
detailPromises.push(db.polls.findAsync({}, {title: 1}));
return Promise.all(detailPromises)
.then(function (data) {
oldPollNames = data.pop().map(function (item) { return item.title; });
return data;
})
.then(function (htmls) {
var newPolls = [];
htmls.forEach(function (i, html) {
// some processing of html using oldPollNames (newPools.push...)
});
return newPolls;
})
.then(/* insert into db */);
What I'm doing is: waiting for db + waiting for html requests and then process booth.
I'm wondering if it would make more sense / be more performant to do following:
var detailPromises = links.map(function (link) { return requestP(link) });
return db.polls.findAsync({}, {title: 1}).then(function(polls) {
var oldPollNames = polls.map(function (item) { return item.title; });
var newPolls = [];
detailPromises = detailPromises.map(function (p) {
return p.then(function (html) {
// some processing of html using oldPollNames (newPools.push...)
})
});
return Promise.all(detailPromises)
.done(function () {
// insert newPolls into db
});
});
The advantage of the second approach imo is that each request gets (already) handled when it is done and can do it's procesing before all / other promises are fulfilled.

I'm wondering if it would make more sense / be more performant
Yes, it would be more performant to process each html request separately and as fast as possible, instead of waiting for all of them and then processing them together in a huge loop. Not only will they processed earlier, you also avoid a possibly long-running loop of heavy processing.
However, the approach also has its drawbacks: it's more complicated to implement. The code you've given is prone to reporting Unhandled Rejections if any of the detailPromises rejects before the database query fulfills, or if any of them rejects and the database query is rejected as well. To prevent that, you'll need to use Promise.all on all the promises anyway:
var detailPromises = links.map(requestP);
var resultPromise = db.polls.findAsync({}, {title: 1}).then(function(polls) {
var oldPollNames = polls.map(function(item) { return item.title; });
var newPollPromises = detailPromises.map(function(p, i) {
return p.then(function(html) {
// some processing of html
});
});
return Promise.all(newPollPromies) // not the detailPromises!
.then(function(newPolls) {
// insert newPolls into db
});
});
return Promise.all([resultPromise].concat(detailPromises)).then(function(r) {
return r[0];
});

In addition to Bergi's answer I think I found another likewise solution:
var detailPromises = links.map(requestP);
var pollNamePromise = db.polls.findAsync({}, {title: 1}).then(function(polls) {
return polls.map(function (item) { return item.title; });
});
var newPromises = detailPromises.map(function(p) {
return Promise.join(p, pollNamePromise, function(html, oldPollNames) {
// some processing of html using oldPollNames (newPools.push...)
});
});
Promise.all(newPromises).then(function(newPolls) {
// insert newPolls into db
});
Edit Changed from Promise.all to Promise.join which is available in Bluebird

Related

return after a series of functions in javascript

I have tried to do this with q as well as async, but haven't been able to seem to make it work. After trying those I tried my own way. I didn't think this would work, but I thought I would give it a try. I am confused since there is a callback within a callback in a sense. Here is the function I am wanting to do:
var getPrice = function(theData) {
var wep = theData.weapon;
var completed = 0;
for (i = 0; i < theData.skins.length; i++) {
var currSkin = theData.skins[i];
theData.skinData[currSkin] = {};
for (k = 0; k < wears.length; k++) {
csgomarket.getSinglePrice(wep, currSkin, wears[k], false,
function(err, data) {
completed++;
if (!err) {
theData.skinData[data.skin][data.wear] = data;
}
if (completed === theData.skins.length*wears.length) {
return theData;
}
})
}
}
}
I know these kinds of issues are common in javascript as I have ran into them before, but not sure how to solve this one. I am wanting to fill my object with all the data returned by the method:
csgomarket.getSinglePrice(wep, currSkin, wears[k], false,
function(err, data) { });
Since each call to getSinglePrice() sends off a GET request it takes some time for the responses to come back. Any suggestions or help would be greatly appreciated!
First csgomarket.getSinglePrice() needs to be promisified. Here's an adapter function that calls csgomarket.getSinglePrice() and returns a Q promise.
function getSinglePriceAsync(wep, skin, wear, stattrak) {
return Q.Promise(function(resolve, reject) { // may be `Q.promise(...)` (lower case P) depending on Q version.
csgomarket.getSinglePrice(wep, skin, wear, stattrak, function(err, result) {
if(err) {
reject(err);
} else {
resolve(result);
}
});
});
}
Now, you want getPrice() to return a promise that settles when all the individual getSinglePriceAsync() promises settle, which is trivial :
var getPrice = function(theData) {
var promises = [];//array in which to accumulate promises
theData.skins.forEach(function(s) {
theData.skinData[s] = {};
wears.forEach(function(w) {
promises.push(getSinglePriceAsync(theData.weapon, s, w, false).then(function(data) {
theData.skinData[data.skin][data.wear] = data;
}));
});
});
//return a single promise that will settle when all the individual promises settle.
return Q.allSettled(promises).then(function() {
return theData;
});
}
However, theData.skinData[data.skin][data.wear] will simplify slightly to theData.skinData[s][w] :
var getPrice = function(theData) {
var promises = [];//array in which to accumulate promises
theData.skins.forEach(function(s) {
theData.skinData[s] = {}; //
wears.forEach(function(w) {
promises.push(getSinglePriceAsync(theData.weapon, s, w, false).then(function(data) {
theData.skinData[s][w] = data;
}));
});
});
//return a single promise that will settle when all the individual `promises` settle.
return Q.allSettled(promises).then(function() {
return theData;
});
}
This simplification would work because the outer forEach(function() {...}) causes s to be trapped in a closure.
As getPrice() now returns a promise, it must be used as follows :
getPrice(myData).then(function(data) {
// use `data` here.
}).catch(function(e) {
//something went wrong!
console.log(e);
});
Your way to do is very complex.
I think the best way is to do 1 request for all prices. Now, for each price you do a request.
If you have a list (array) with data that's need for the request, the return value should be a list with the prices.
If approach above is not possible, you can read more about batching http requests: http://jonsamwell.com/batching-http-requests-in-angular/
Need some clarification - Are you trying to run this on the Client side? Looks like this is running inside a nodejs program on server side. If so, wouldn't you rather push this logic to the client side and handle with Ajax. I believe the browser is better equipped to handle multiple http request-responses.
Since you didn't post much info about your csgoMarket.getSinglePrice function I wrote one that uses returns a promise. This will then allow you to use Q.all which you should read up on as it would really help in your situation.
I've created an inner and outer loop arrays to hold our promises. This code is completely untested since you didn't put up a fiddle.
var getPrice = function(theData) {
var wep = theData.weapon;
var completed = 0;
var promises_outer = [] //array to hold the arrays of our promises
for (var i = 0; i < theData.skins.length; i++) {
var currSkin = theData.skins[i];
theData.skinData[currSkin] = {};
var promises_inner = [] // an array to hold our promises
for (var k = 0; k < wears.length; k++) { //wears.length is referenced to below but not decalared anywhere in the function. It's either global or this function sits somewhere where it has access to it
promises_inner.push(csgomarket.getSinglePrice(wep, currSkin, wears[k], false))
}
promises_outer.push(promises_inner)
}
promises_outer.forEach(function(el, index){
var currSkin = theData.skins[index]
theData.skinData[currSkin] = {}
Q.all(el).then(function(data){ //data is an array of results in the order you made the calls
if(data){
theData.skinData[data.skin][data.wear] = data
}
})
})
}
var csgomarket = {}
csgomarket.getSinglePrice = function(wep, currSkin, wears, someBoolean){
return Q.promise(function (resolve, reject){
//do your request or whatever you do
var result = true
var data = {
skin : "cool one",
wear : "obviously"
}
var error = new Error('some error that would be generated')
if(result){
resolve(data)
} else {
reject(error)
}
})
}

How to get Protractor deferred Promise to work?

Scenario 1) Stale Element Reference error
it("should have all elements", function (done) {
var def = protractor.promise.defer();
var prm = browser.findElements(by.css("jive")).then((elements) => {
elements.forEach((ele) => {
var id = ele.getId();
var loc = ele.getLocation();
var inner = ele.getInnerHtml();
var text = ele.getText();
var tag = ele.getTagName();
});
def.then((wtf) => {
debugger;
});
});
done();
});
I thought that the code above would have a ton of promises on the queue after running through the iteration on all elements. But when the def.then statement runs Selenium is telling me I have Stale Elements. The iteration is running through the elements for that css.
I was hoping to get an array of resolved promises for everything the iteration requested...
Scenario 2) Gives Stale Element Reference
var promises = Array<webdriver.promise.Promise<any>>();
var allElements: webdriver.IWebElement[] = [];
it("should have all elements", function (done) {
var alllinks: webdriver.WebElement[];
browser.controlFlow().execute(() => {
browser.findElements(by.tagName("a")).then((works) => {
alllinks = works;
alllinks.forEach((ele) => {
promises.push(ele.getAttribute("href"));
promises.push(ele.getId());
promises.push(ele.getInnerHtml());
promises.push(ele.getLocation());
});
//getting stale reference here...
protractor.promise.all(promises).then((wtf) => {
debugger;
});
debugger;
});
});
Please advise.
I think you need the map():
var links = element.all(by.tagName("a")).map(function (link) {
return {
href: link.getAttribute("href"),
id: link.getId(),
innerHTML: link.getInnerHtml(),
location: link.getLocation()
}
});
Now the links would contain a promise resolving into an array of objects.
My tiny example might be helpful for you:
viewBookingDetailsPage.roomtypeAttributeForParticularDay = function (day, roomTypeNumber, selector) {
var deferred = protractor.promise.defer();
element.all(by.repeater(viewBookingDetailsPage.guestroomInfo.allDays)).then(function (days) {
days[day].all(by.repeater(viewBookingDetailsPage.guestroomInfo.roomTypesInDay)).then(function (roomTypes) {
deferred.fulfill(roomTypes[roomTypeNumber].$(selector));
});
});
return deferred.promise;
};
You need to use fulFill to return result after promise will be successfully resolved.

Loop is completing before all the promises responses do

So i have this function where i need to combine multiple promises responses, but after some reading i realized promises are async so in this case my loop is going to complete before all the responses do. Should i need to use something like $q.all in this case? How can i improve this piece of code? Thanks..
$scope.messages = [];
function getPastMessages(data) {
angular.forEach(data, function(item) {
Message.get(item.id).then(function(msg) {
if (msg.data.is_private === false) {
User.getPictures(msg.data.user.id).then(function(pics) {
msg.data.user.pictures = pics.data;
});
} else {
User.get(msg.data.im.sender).then(function(sender) {
msg.data.im.sender = sender.data;
User.get(msg.data.im.reciever).then(function(reciever) {
msg.data.im.reciever = reciever.data;
});
});
}
console.log(msg.data); // SHOW 4 OBJECTS CORRECT
$scope.messages.push(msg.data);
console.log($scope.messages); // SHOW ARRAY OF 6 OBJECTS ????????
})
});
};
Without a working example it was difficult to fully understand the context of your code, but you can do something similar to this.
The basic idea is to create a list of promises that you need to wait for. Each of the promises within this list should return a result (presumably msg.data). Using $q.all, you'll get a list of results (one from each promise) at the end. Note that things returned within a .then get wrapped in promises if they aren't already promises.
$scope.messages = [];
function getPastMessages(data) {
var promises = [];
angular.forEach(data, function(item) {
promises.push(getMessage(item));
});
return $q.all(promises);
}
function getMessage(item) {
return Message.get(item.id).then(function(msg) {
if (msg.data.is_private === false) {
return User.getPictures(msg.data.user.id).then(function(pics) {
msg.data.user.pictures = pics.data;
return msg.data;
});
} else {
return User.get(msg.data.im.sender).then(function(sender) {
msg.data.im.sender = sender.data;
return User.get(msg.data.im.reciever).then(function(reciever) {
msg.data.im.reciever = reciever.data;
return msg.data;
});
});
}
});
}
Usage:
getPastMessages(data).then(function(results) {
for (var i = 0; i < results.length; i++) {
$scope.messages.push(results[i]);
}
});
You can only rely on promises to have resolved when you're inside their callback functions.
var messages = [];
somethingAsync().then(function(data){
messages.push(data);
});
console.log(messages.length)
might return 0 or 1 depending on how long somethingAsync takes; you can't rely on it having completed.
Instead you should do your debugging from inside the callback function:
var messages = [];
somethingAsync().then(function(data){
messages.push(data);
console.log(messages.length)
});
This will always return 1.

Javascript promises chaining

I'm using Parse Cloud functions for mobile apps, but all of them follow asynchronous nature. Hence to overcome that nature, I started using javascript Promises.
But promises also not giving me the desired output.
The problem is : second-then is executed after the third-then. And in third-then the parameter of getRecommendedGroup groups is getting []
getRecommendedGroup(request.params.email, groups, function(data){
this is a callback
});
Basically groups is an output of 2nd then.
So how should I write my code inorder to make 2nd-then execute before 3rd-then.
Below is the code snippet
Parse.Cloud.define("getGroup", function(request, response) {
var grpMember = new Parse.Query("GroupMember"),
groupIds = [],
groupObj = {};
grpMember.equalTo("user", request.params.email);
//from the groupMember we're taking groupId
grpMember.find()
.then(function(grpMemberResponse) {
grpMemberResponse.forEach(function(value, index) {
var memberObj = value;
groupIds.push(memberObj.get("groupId").id);
});
return groupIds;
})
//with this groupId we're retriving group and pushing it to an groupArr
.then(function(groupIds) {
var groupArr = [];
var promises = [];
groupIds.forEach(function(value, index) {
alert("GroupId :: " + value);
promises.push(findGroup(value, function(group) {
groupArr.push(group);
});
});
return Parse.Promise.when(promises);
})
.then(function(groups) {
alert("GroupArr :: " + JSON.stringify(groups));
getRecommendedGroup(request.params.email, groups, function(data) {
groupObj["own"] = groups;
groupObj["recommended"] = data;
response.success(groupObj);
});
});
});
var findGroup = function(objectId, callback) {
var groupQuery = new Parse.Query("Group");
groupQuery.get(objectId, {
success: function(group) {
alert("Group Obj Id ::" + group.id);
callback(group);
},
error: function(error) {
alert("Error while finding Group " + error);
}
});
};
The second then is running second, but it is calling an asynch function. In order to sequence properly, those promises being launched by the loop must be fulfilled. Fortunately, Promise.when() does that...
.then(function(groupIds) {
var findPromises = []
groupIds.forEach(function(value, index) {
findPromises.push(findGroup(value, function(group));
)};
return Parse.Promise.when(findPromises);
}).then() {
// arguments is now a (var arg) array of results from running
// all of the findPromises promises
});
Edit - In order for this to work, the code needs to push a promise to do the find. That means findGroup must return a promise...
function findGroup(objectId) {
var groupQuery = new Parse.Query("Group");
return groupQuery.get(objectId);
}

Bluebird Promising the result of a heavy function

I've been using Bluebird a lot recently on a HAPI API development. I've just run into my first real problem, that perhaps my understanding or naivety has me stumped.
The following code is an example of what I am facing:-
var Promise = require('bluebird'),
stuff = require('../stuff');
module.exports = {
getSomething: function(request, reply) {
var p = Promise.resolve();
p = p.then(function() {
return db.find() //etc. etc.
});
p = p.then(function(resultFromPromise) {
//problems begin here
var data = stuff.doSomeReallyLongAndBoringFunction(resultFromPromise);
return data;
});
p.then(function(data) {
//no data here.
});
};
};
I've commented where the problems usually begin. the stuff.doSomeReallyLongAndBoringFunction() returns an object (using more promises concidently) and it's this object I want to access, but //no data here always fires before data returns. stuff.doSomeReallyLongAndBoringFunction() continues to run regardless and completes successfully, but after the code goes async, I don't know how to promise that function's return value back.
Can anyone offer any guidance? Please accept my apologies for any naivety in the question!
Help as always, is appreciated
NB just for clarity, stuff.doSomeReallyLongAndBoringFunction() does not return a Promise itself. Although, I did try return new Promise(reject, resolve) { }); manual wrap. It is simply a function that uses promises itself (successfully) to get data.
Update 1
stuff.doSomeReallyLongAndBoringFunction() is too big to post directly, but it does something like this:-
var Promise = require('bluebird'),
rp = require('request-promise');
module.exports = {
doSomeReallyLongAndBoringFunction: function() {
var p = Promise.resolve();
p = p.then(function() {
return db.find() //etc. etc.
});
p.then(function() {
rp(options).then(function(response){
//get some data from remote location
}).then(function(dataFromService) {
//do some jiggery pokery with said data
var marshalledData = dataFromService;
db.something.create({
Field: 'something'
}).exec(function(err, saved) {
return marshalledData;
});
});
}).catch(function(err) {
});
};
};
Update 2
Thank you Justin for your help. Here is the actual code, perhaps this may help?
Promise.resolve()
.then(function() {
if(typeof utils.intTryParse(place) !== 'number') {
return foursquare.createPlaceFromFoursquare(sso, place, request, reply);
} else {
return { Place: { PlaceId: place }};
}
}).then(function(placeObj) {
console.log('Place set as', placeObj); //always returns undefined, despite function actually completing after async op...
});
If your doSomeReallyLongAndBoringFunction is really running asynchronously, then it doesn't make sense to run it the way you have setup.
Edit - Here's a simple explanation of the way your code looks to be running vs a refactored version. It's been simplified , so you'll need to fill in the relevant sections with your actual implementation.
var Promise = require('bluebird');
function myAsync() {
setTimeout(function(){
return 'done sleeping';
}, 2000);
};
//The way your code is running
Promise.resolve()
.then(function(){
return 'hello';
})
.then(function(done){
console.log(done);
return myAsync(); //your error is here
})
.then(function(done){
console.log(done);
});
//refactored
Promise.resolve()
.then(function(){
return 'hello';
})
.then(function(done){
console.log(done);
return new Promise(function(resolve) {
setTimeout(function(){
resolve('done sleeping');
}, 2000);
});
})
.then(function(done){
console.log(done);
});
just for clarity, stuff.doSomeReallyLongAndBoringFunction() does not return a Promise itself.
And that's your problem. As it does something asynchronous and you want to get its result, it should return a promise. In fact, that's the case for every asynchronous function, especially then callbacks! It should be something like
module.exports = {
doSomeReallyLongAndBoringFunction: function() {
return db.find()
// ^^^^^^
.then(function() {
return rp(options).then(function(response){
// ^^^^^^
//get some data from remote location
}).then(function(dataFromService) {
//do some jiggery pokery with said data
var marshalledData = dataFromService;
return db.something.create({
// ^^^^^^
Field: 'something'
}).execAsyc();
});
}).catch(function(err) {
});
}
};
Your getSomething method has the same issues, and should look like this:
var createPlace = Promise.promisify(foursquare.createPlaceFromFoursquare);
module.exports = {
getSomething: function(request) {
var p;
if (typeof utils.intTryParse(place) !== 'number')
p = createPlace(sso, place, request); // this returns a promise!
else
p = Promise.resolve({Place: {PlaceId: place}});
return p.then(function(placeObj) {
// ^^^^^^
console.log('Place set as', placeObj);
});
}
};
See also these generic rules for promise development.
doSomeReallyLongAndBoringFunction needs to look like this:
doSomeReallyLongAndBoringFunction: function(param) {
var resolver = Promise.defer();
/*
* do some asynchronous task and when you are finished
* in the callback, do this:
*/
resolver.resolve(resultFromAsyncTask);
/*
*
*
*/
return resolver.promise;
}

Categories