multiple promises running in parallel, $q.all needs chaining? - javascript

I have 3 parallel promises or api requests once all teh three are done, I need to call another api request based on the second promise and then finally call .then( of $q.all
Here is the code
getAllLocations() {
//make a promise call for all here .
var promise = [];
̶p̶r̶o̶m̶i̶s̶e̶.̶p̶u̶s̶h̶(̶t̶h̶i̶s̶.̶g̶e̶t̶A̶l̶l̶L̶o̶c̶a̶t̶i̶o̶n̶s̶(̶I̶d̶)̶.̶t̶h̶e̶n̶(̶
promise.push(this.getLocations(Id).then(
(locationsData) => {
this.locations = locationsData;
}));
promise.push(this.getAllStates(Id).then(
(resp) => {
this.states = resp.data;
}));
promise.push(this.getTerritories(Id).then(
(resp) => {
this.utilizations = resp.data;
}));
$q.all(promise).then(() => {
var nodePromise = [];
angular.forEach(this.states, function(node) {
var nodeId = node.Id;
nodePromise.push(this.getNodeHealthSummary(nodeId).then(
(resp) => {
node.healthStatus = resp.data.operationalStatus;
}));
this.$q.all(nodePromise).then(() => {
var index = this.states.indexOf(node);
this.states.splice(index, 1, angular.copy(node));
});
},this);
}).then(() => {
for (var i = 0; i < this.locations.length; i++) {
//do something here with this.states
}
this.gridData = this.locations;
});
}
I need this.states updated with healthStatus property when i am in the for loop of this.locations. ( the last.then )
However , i see that this.locations for loop is done ahead before the node.healthStatus property is set on each state.
How can this be done? Using Promises instead of $q is fine. Please let me know how can i achieve this , i have tried all in vain

The inner $q.all is called in each iteration of the forEach loop, and gets as argument the array that is being populated during that forEach loop. This is obviously not right; it should be called only once, and its result should be the return value of the then callback.
So instead of this block:
$q.all(promise).then(() => {
var nodePromise = [];
angular.forEach(this.states, function(node) {
var nodeId = node.Id;
nodePromise.push(this.getNodeHealthSummary(nodeId).then(
(resp) => {
node.healthStatus = resp.data.operationalStatus;
}));
this.$q.all(nodePromise).then(() => {
var index = this.states.indexOf(node);
this.states.splice(index, 1, angular.copy(node));
});
},this);
}).then( ......
Do this:
$q.all(promise).then(() => {
return $q.all(this.states.map((node, index) => {
return this.getNodeHealthSummary(node.Id).then(resp => {
node.healthStatus = resp.data.operationalStatus;
this.states[index] = angular.copy(node);
});
}));
}).then( ......

Related

Javascript functions not called in correct order, need async

The code snippet below is not running in the proper order... the for loop is calling the downloadFile function call over every iteration before any other functions can resolve, which is affecting the number of links returned to the final callback.
What I need is an loop iteration to finish only once the appendLinksList function has resolved and returned a link. I'm assuming I need async to block the functions? How would I use async to get the proper flow instead of the downloadFiles function being called before anything else can resolve a value?
Note that the callback functions have been defined above this code snippet
const entries [ /* Array of objects containing a path_display property */];
let links = [];
for(let i = 0; i < entries.length; i++) {
const appendLinksList = link => {
if(i !== entries.length - 1) return links = [...links, link];
return callback(null, links);
};
const downloadFile = path => {
Dropbox.filesGetTemporaryLink({ path })
.then(file => {
return appendLinksList(file.link);
})
.catch(err => {
return res.status(500).json(err.message);
});
};
downloadFile(entries[i].path_display);
};
You can wrap your loop in an immediately invoked async function.
const linksResolved = await (async() => {
let links = [];
for(let i = 0; i < entries.length; i++) {
const appendLinksList = link => {
if(i !== entries.length - 1) return links = [...links, link];
return callback(null, links);
};
const path = entries[i].path_display;
await Dropbox.filesGetTemporaryLink({ path })
.then(file => {
return appendLinksList(file.link);
})
.catch(err => {
return res.status(500).json(err.message);
});
};
};
return links;
})()
However, the result you want (links) will forever be a promise.
Can only be resolved in an async function. Or you can call a callback with links from inside the immediatelyInvokedAsyncFunction

JavaScript promise bypassed resolve and proceed to .then()

I was having some problem with nested promise which resulting in forgotten promise problem.
let promiseList = new Promise((resolve, reject) => {
//first query to retrieve from firebase
query.once( 'value', data => {
var promises = [];
data.forEach(snapshot => {
//get item key
//second query based on item key
var promise = query.once('value');
promises.push(promise);
promise.then(data => {
var itemDetail = data.val();
var receiptID = itemDetail.receiptID;
// third query to find matching receiptID
var query = firebase.database().ref('receipts');
query.once('value', data => {
data.forEach(snapshot => {
snapshot.forEach(childSnapshot => {
if(childSnapshot.key == receiptID){
var branchDetail = childSnapshot.val().branch;
var branchName = branchDetail.branchName;
//console.log('inside promise ' + branchName);
datasetarr.push({branchName: branchName});
}
});
});
});
});
});
// wait till all promises are finished then resolve the result array
Promise.all(promises).then(() => resolve(datasetarr));
});
});
// print out array here
promiseList.then((arr) => {
for(var i = 0; i < arr.length; i++){
console.log(arr[i].branchName);
}
});
I managed to print out the data from the console.log with 'inside promise'. However, when I tried to print it out from the .then(), there is nothing shown.
The problem now is it actually ran the .then() first before I resolve the promise.
Any ideas?
I've never used Firebase, but I do know promises.
Check this sample chaining promises, notice the return statements which produce the chaining.
var outerPromise = query.once('value').then(data => {
// Promise array to group 2nd level promises and then do a Promise.all.
var promises = [];
// This will be the main output of the outerPromise.
// We will populate it asynchronously inside our 2nd level promises.
var datasetarr = [];
data.forEach(snapshot => {
// 2nd level promises, will be appended to the promises array.
// and will be enchained with the 3d level promise.
var promise = query.once('value').then(data => {
var itemDetail = data.val();
var receiptID = itemDetail.receiptID;
var query = firebase.database().ref('receipts');
// Third level promise. It's enchained by the return statement.
return query.once('value').then(data => {
data.forEach(snapshot => {
snapshot.forEach(childSnapshot => {
if(childSnapshot.key == receiptID){
var branchDetail = childSnapshot.val().branch;
var branchName = branchDetail.branchName;
//console.log('inside promise ' + branchName);
datasetarr.push({branchName: branchName});
}
});
});
});
});
promises.push(promise);
});
// We wait until 2nd (and third) level promises are ready
// and the return our desired output, the datasetarr
return Promise.all(promises).then(()=> datasetarr);
});
// Since it's all chained, the outerPromise will resolve once all promises are completed
// and we can get the output we supplied in the last chaining.
outerPromise.then((arr) => {
console.log(arr)
});
That's not how promises work, there's seldom if ever a need to nest them. If query.once already returns a promise that's great, but otherwise you'll need to wrap it:
let returnsPromise = value => new Promise(res => query.once(value, data => res(data));
Again, if it already returns a promise that's unnecessary, but I'm not a firebase guy. At any rate, now you can do something like this:
let result = returnsPromise('value')
// run secondary query based on item key
.then(data => Promise.all(data.map(item => returnsPromise(item.key)))
// now do stuff with those results
.then(data => {
return Promise.all(data.map(item => {
let receiptID = item.val().receiptID;
// Note that the same 'wrap if not already returning promise
// idea' is applicable here, but for illustration I'm just
// going to act like this returns a promise.
// Also note that while I've been rather down on nesting
// its more or less necessary here because you need to capture
// the receipt ID from the surrounding scope.
return firebase.database().ref('receipts')
.once('value')
.then(snapshot => {
return snapshot
.filter(x => x.key === receiptID)
.map(x => {
let branch = x.val().branch.branchName;
return {branch: branch};
});
});
}))
// Now we have an array of arrays of results but we want to
// remove the nesting.
.then(arrayOfArrays => arrayOfArrays.reduce((x,y) => { return x.concat(y); }, []));
Now you have a result promise that contains the array of values. You can call then on it and iterate over it:
result.then(arr => arr.forEach(x => console.log(x.branchName)));

Callback after iterating through an array and saving objects

I need to iterate through an array and save every object to the Database.
At the end I need a callback with an array of all of the saved and failed object.
Below is the code I have:
exports.addList = (app, body, callback) => {
var savedObjects = []
var failedObjects = []
body.forEach((element, index) => {
body[index] = _.pick(element, 'userAId','userBId')
db.List.create(element).then((list) => {
savedObjects.push(element)
if (index == body.length - 1) {
callback(savedObjects, failedObjects)
}
}).catch((error) => {
if (error.name === "SequelizeUniqueConstraintError") {
failedObjects.push(element)
if (index == body.length - 1) {
callback(savedObjects, failedObjects)
}
})
})
}
The code above works. Is there a way better to accomplish this?
I would recommend the following approach using Promise.all() to run the db.List.create() in parallel as it will return a Promise. By mapping the body array elements to Promises you can achieve better performance as they will run in parallel (and not have to track the complete count).
exports.addList = (app, body, callback) => {
var savedObjects = [];
var failedObjects = [];
Promise.all(
// map the array to return Promises
body.map(element => {
const list = _.pick(element, 'userAId','userBId');
return db.List.create(list)
.then(() => savedObjects.push(list))
.catch((error) => {
if (error.name === 'SequelizeUniqueConstraintError') {
failedObjects.push(list)
}
})
})
)
// when all Promises have resolved return the callback
.then(() => callback(savedObjects, failedObjects));
}
In your example your complete callback will always fire after the first promise is complete. This is because the create function is asynchronous while the surrounding loop is not, therefore the loop will have already completed by the time your first callback is triggered.
In your scenario this means element and index will always be the last in the loop. One way around this would be to move your promise chain into it's own function.
In this example I've used an extra flag to track the number of completed promises to trigger your complete method.
exports.addList = (app, body, callback) => {
var savedObjects = []
var failedObjects = []
var complete = 0;
function createElement(element){
db.List.create(element).then((list) => {
savedObjects.push(element)
}).catch((error) => {
if (error.name === "SequelizeUniqueConstraintError") {
failedObjects.push(element)
}
}).finally(() => {
complete++;
if(complete == body.length) {
callback(savedObjects, failedObjects)
}
});
}
body.forEach((element, index) => {
body[index] = _.pick(element, 'userAId','userBId');
createElement(element);
})
}

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.

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);
}

Categories