Promises not running in order - javascript

I have a function in my controller that adds users to a group, Once a user has been assigned to a group, the list of groups available should decrease. I tried using promises in my function to control the flow but based on the console log, all my groupServices are running first, then the userServices are running after, preventing the list of Available groups to update correction.
Controller Function:
$scope.addUserToGroup = function (){
var defer = $q.defer();
defer.promise.then(function (){
userService.addUserToGroup(
$scope.selectedUser,
$scope.selectedAvailableGroups,
$scope.assignedGroups,
$scope.availableGroups,
$scope.groups
);
}).then(compare());
defer.resolve();
};
function compare(){
console.log('comparing assigned with all ');
$scope.compareGroups = groupService.compareGroups();
}
I'm using promises trying to make sure things run in order but based on my console output it doesn't seem to be the case.
User Service function
var addUserToGroup = function (selectedUser, selectedAvailableGroups, assignedGroups, availableGroups, groups){
console.dir(selectedUser);
console.dir(selectedAvailableGroups);
console.dir(assignedGroups);
console.dir(availableGroups);
console.dir(groups);
var deferred = $q.defer();
var addPromise = [];
var selectLength = selectedAvailableGroups.length;
//Add user to selected groups on server
deferred.promise
.then(function (){
for (var i = 0; i < selectLength; i++){
addPromise[i] = $().SPServices({
operation: "AddUserToGroup",
groupName: selectedAvailableGroups[i].name,
userLoginName: selectedUser.domain
});
};
})
.then(function (){
//when promise finished, push changes to availableGroups
for (var i = 0; i < selectLength; i++){
assignedGroups.push(selectedAvailableGroups[i]);
console.log(selectedUser.name + " added to: " + selectedAvailableGroups[i].name);
};
});
//Run
deferred.resolve();
}
Group Service function:
var compareGroups = function () {
//Comparing assigned groups with allGroups to return available groups
var assignedGroupsIds = {};
var groupsIds = {};
var result = []
availableGroups = [];
console.log('assigned');
console.dir(assignedGroups);
console.log('avail');
assignedGroups.forEach(function (el, i) {
assignedGroupsIds[el.id] = assignedGroups[i];
});
allGroups.forEach(function (el, i) {
groupsIds[el.id] = allGroups[i];
});
for (var i in groupsIds) {
if (!assignedGroupsIds.hasOwnProperty(i)) {
result.push(groupsIds[i]);
availableGroups.push(groupsIds[i]);
}
};
console.dir(result);
console.dir(availableGroups);
}
Console Log:
comparing assigned with all userCtrl.js:47
assigned groups groupServices.js:63
Array[8] groupServices.js:64
available gruops groupServices.js:65
Array[3] groupServices.js:82
Array[3] groupServices.js:83
Object userServices.js:38
Array[1] userServices.js:39
Array[8] userServices.js:40
Array[4] userServices.js:41
Array[11] userServices.js:42
User added to: Test Group 4 userServices.js:64

You are using promises in wrong way.
The first problem is here:
}).then(compare());
You are trying to register result of execution of compare function as a callback, instead of registering just compare function like this:
}).then(compare);
That's why it executes first, then calls groupService.compareGroups() and only after it completes, you are calling defer.resolve() and your first registered callback executes. That's why you see your current console output.
You need to modify User Service and Controller in following way to get it work (Using $q service to work with promises):
Controller function:
function compare(){
console.log('comparing assigned with all ');
$scope.compareGroups = groupService.compareGroups();
}
$scope.addUserToGroup = function (){
userService.addUserToGroup(
$scope.selectedUser,
$scope.selectedAvailableGroups,
$scope.assignedGroups,
$scope.availableGroups,
$scope.groups
).then(compare);
};
User Service function:
(Assuming, that $().SPServices returns Promise)
var addUserToGroup = function (selectedUser, selectedAvailableGroups, assignedGroups, availableGroups, groups){
console.dir(selectedUser);
console.dir(selectedAvailableGroups);
console.dir(assignedGroups);
console.dir(availableGroups);
console.dir(groups);
var deferred = $q.defer();
var addPromise = [];
var selectLength = selectedAvailableGroups.length;
//Add user to selected groups on server
for (var i = 0; i < selectLength; i++){
addPromise[i] = $().SPServices({
operation: "AddUserToGroup",
groupName: selectedAvailableGroups[i].name,
userLoginName: selectedUser.domain
});
}
$q.all(addPromise).then(function (){
//when promise finished, push changes to availableGroups
for (var i = 0; i < selectLength; i++){
assignedGroups.push(selectedAvailableGroups[i]);
console.log(selectedUser.name + " added to: " + selectedAvailableGroups[i].name);
};
//Finish function
deferred.resolve();
});
return deferred.promise;
}

Related

Wait until multiple asynchronous functions complete before executing

I have a Javascript for loop which runs through an array of database records (that have been extracted already).
I want to know when all the subsequent asynchronous actions have completed but I can't seem to do it.
For each record, the code runs a number of functions which return promises and then resolve (which then triggers another function to get more information, etc). This all works ok, but I can't figure out how to gather up each "FOR" iteration and detect when all records have been processed. Basically, I want to use a "throbber" and have the throbber remain until all processing has been completed.
Code is below (I've removed some extraneous info)...
for (var i = 0; i < systemArray.length; i++) {
// ***** FOR EACH SYSTEM ***** //
var currRecord = systemArray[i];
// SECTION REMOVED //
// GET SYSTEM LINES
var thisSystem = AVMI_filterArray("8.9", currRecord);
var thisSystemName = thisSystem[1].value;
var thisSystemRID = thisSystem[0].value;
// GET CHILDREN RIDS
AVMI_getChildren(systemLinesTable, thisSystemRID, systemLinesFID).done(function(ridList, sysRID)
{
var thisDiv = "div#" + sysRID;
// GET RECORD INFO FOR EACH RID
AVMI_getMultipleRecordInfoFromArray(ridList, systemLinesTable).done(function(systemLinesArray)
{
if (systemLinesArray != "" && systemLinesArray != null) {
systemLinesArray = systemLinesArray.sort(propComparator("10"));
x = AVMI_tableCombiner("System Lines", systemLinesArray, systemLinesCLIST, "skip3Right hbars xsmallText");
$(thisDiv).append(x);
} else {
$(thisDiv).append("<p>No System Lines...</p>");
}
}
);
}
);
} // ***** FOR EACH SYSTEM ***** //
AVMI_throbberClose(); // THIS, OF COURSE, EXECUTES ALMOST IMMEDIATELY
Here is function 1
///////////////////////////////////////////////////////////////
// Get related records using master
///////////////////////////////////////////////////////////////
function AVMI_getChildren(AVMI_db, AVMI_rid, AVMI_fid, AVMI_recText) {
var AVMI_query = "{" + AVMI_fid + ". EX. " + AVMI_rid + "}";
var AVMI_ridList = [];
var dfd2 = $.Deferred();
$.get(AVMI_db, {
act: "API_DoQuery",
query: AVMI_query,
clist: "3",
includeRids: "1"
}).then(function(xml1) {
$(xml1).find('record').each(function(){
var AVMI_record = $(this);
var AVMI_childRID = AVMI_record.attr("rid");
AVMI_ridList.push(AVMI_childRID);
});
AVMI_throbberUpdate("Found " + AVMI_ridList.length + " " + AVMI_recText + "...");
dfd2.resolve(AVMI_ridList, AVMI_rid);
});
return dfd2.promise();
};
And function 2
///////////////////////////////////////////////////////////////
// Get record info for each array member
///////////////////////////////////////////////////////////////
function AVMI_getMultipleRecordInfoFromArray(ridList, AVMI_db, AVMI_recType) {
var promises = [];
var bigArray = [];
$.each(ridList, function (index,value) {
var def = new $.Deferred();
var thisArray = [];
$.get(AVMI_db, { //******* ITERATIVE AJAX CALL *******
act: 'API_GetRecordInfo',
rid: value
}).then(function(xml2) {
AVMI_throbberUpdate("Got " + AVMI_recType + " " + value + "...");
$(xml2).find('field').each(function() {
var $field = {};
$field.fid = $(this).find('fid').text();
$field.name = $(this).find('name').text();
$field.value = $(this).find('value').text();
thisArray.push($field);
});
thisArray = thisArray.sort(AVMI_ArrayComparator);
bigArray.push(thisArray);
def.resolve(bigArray);
});
promises.push(def);
});
return $.when.apply(undefined, promises).promise();
};
Any ideas of how to structure this? I've tried all sorts of things with $.Deferred but I can't quite figure it out...
You do exactly the same thing you did in AVMI_getMultipleRecordInfoFromArray: Collect the promises in an array and use $.when (or Promise.all) to wait until they are resolved.
You can simply use .map in here which also takes care of the "function in a loop" problem:
var promises = systemArray.map(function(currRecord) {
// ...
return AVMI_getChildren(...).done(...);
});
$.when.apply(undefined, promises).done(function() {
AVMI_throbberClose();
});
You should have to disable the async property of ajax. By default it is set to true. It means that your doesn't wait for your ajax response. That why it is returning you undefined value and you have to set it to false. So your code will wait your request to complete.
So all you have to do is.
$.ajax({
url: '',
type: '',
async: false,
success: function(data){
}
});

Javascript variable is an array of objects but can't access the elements

I am using Firebase database and Javascript, and I have code that will get each question in each category. I have an object called category will contain the name, the questions, and the question count, then it will be pushed into the list of categories (questionsPerCategory). Inside the the callback function I just do console.log(questionsPerCategory). It prints the object (array) that contains the categories and questions. Now my problem is that when I do console.log(questionsPerCategory[0]) is says it's undefined, I also tried console.log(questionsPerCategory.pop()) since it's an array but it's also undefined. Why is that? Below is the code and the image of the console log. Additional note: CODE A and C are asynchronous, CODE B and D are synchronous.
this.getQuestionsForEachCategory = function(callback, questions, questionsPerCategory) {
var ref = firebase.database().ref('category');
var questionRef = firebase.database().ref('question');
console.log('get questions for each category');
// CODE A
ref.once("value", function(snapshot) {
// CODE B
snapshot.forEach(function(childSnapshot) {
var key = childSnapshot.key;
var childData = childSnapshot.val();
var category = {
category_name: childData.category_name
};
// CODE C
questionRef.orderByChild("category_name").equalTo(childData.category_name).once("value", function(questionSnapshot){
var count = 0;
var q = [];
// CODE D
questionSnapshot.forEach(function(childQuestionSnapshot) {
var questionObj = childQuestionSnapshot.val();
count++;
questions.push(questionObj.question);
q.push(questionObj.question);
});
category.questions = q;
category.questionCount = count;
questionsPerCategory.push(category);
});
});
callback(questionsPerCategory);
});
};
The callback(questionsPerCategory); should happen when all async calls are finished.
Right now the questionsPerCategory is not ready when the callback is called.
I would use Promise API to accomplish this.
Depending on the Promise library you are using, this can be accomplished in a different ways, e.g. by using bluebird it looks like you need map functionality.
Try this code:
this.getQuestionsForEachCategory = function(callback, questions) {
var ref = firebase.database().ref('category');
var questionRef = firebase.database().ref('question');
console.log('get questions for each category');
var questionsPerCategory = [];
// CODE A
ref.once("value", function(snapshot) {
// CODE B
snapshot.forEach(function(childSnapshot) {
var key = childSnapshot.key;
var childData = childSnapshot.val();
var category = {
category_name: childData.category_name
};
// CODE C
questionRef.orderByChild("category_name").equalTo(childData.category_name).once("value", function(questionSnapshot){
var count = 0;
var q = [];
// CODE D
questionSnapshot.forEach(function(childQuestionSnapshot) {
var questionObj = childQuestionSnapshot.val();
count++;
questions.push(questionObj.question);
q.push(questionObj.question);
});
category.questions = q;
category.questionCount = count;
questionsPerCategory.push(category);
});
callback(questionsPerCategory);
});
});
};

Javascript array shows in console, but i cant access any properties in loops

I really try my damndest not to ask, but i have to at this point before I tear my hair out.
By the time the js interpreter gets to this particular method, I can print it to the console no problem, it is an array of "event" objects. From FireBug I can see it, but when I try to set a loop to do anything with this array its as if it doesn't exist. I am absolutely baffled......
A few things:
I am a newbie, I have tried a for(var index in list) loop, to no avail, I have also tried a regular old for(var i = 0; i < listIn.length; i++), and I also tried to get the size of the local variable by setting var size = listIn.length.
As soon as I try to loop through it I get nothing, but I can access all the objects inside it from the FireBug console no problem. Please help, even just giving me a little hint on where I should be looking would be great.
As for the array itself, I have no problems with getting an array back from PHP in the form of: [{"Event_Id":"9", "Title":"none"}, etc etc ]
Here is my code from my main launcher JavaScript file. I will also post a sample of the JSON data that is returned. I fear that I may be overextending myself by creating a massive object in the first place called content, which is meant to hold properties such as DOM strings, settings, and common methods, but so far everything else is working.
The init() function is called when the body onload is called on the corresponding html page, and during the call to setAllEvents and setEventNavigation I am lost.
And just to add, I am trying to learn JavaScript fundamentals before I ever touch jQuery.
Thanks
var dom, S, M, currentArray, buttonArray, typesArray, topicsArray;
content = {
domElements: {},
settings: {
allContent: {},
urlList: {
allURL: "../PHP/getEventsListView.php",
typesURL: "../PHP/getTypes.php",
topicsURL: "../PHP/getTopics.php"
},
eventObjArray: [],
buttonObjArray: [],
eventTypesArray: [],
eventTopicsArray: []
},
methods: {
allCallBack: function (j) {
S.allContent = JSON.parse(j);
var list = S.allContent;
for (var index in list) {
var event = new Event(list[index]);
S.eventObjArray.push(event);
}
},
topicsCallBack: function(j) {
S.eventTopicsArray = j;
var list = JSON.parse(S.eventTopicsArray);
topicsArray = list;
M.populateTopicsDropDown(list);
},
typesCallBack: function(j) {
S.eventTypesArray = j;
var list = JSON.parse(S.eventTypesArray);
typesArray = list;
M.populateTypesDropDown(list);
},
ajax: function (url, callback) {
getAjax(url, callback);
},
testList: function (listIn) {
// test method
},
setAllEvents: function (listIn) {
// HERE IS THE PROBLEM WITH THIS ARRAY
console.log("shall we?");
for(var index in listIn) {
console.log(listIn[index]);
}
},
getAllEvents: function () {
return currentArray;
},
setAllButtons: function (listIn) {
buttonArray = listIn;
},
getAllButtons: function () {
return buttonArray;
},
setEventNavigation: function(current) {
// SAME ISSUE AS ABOVE
var l = current.length;
//console.log("length " + l);
var counter = 0;
var endIndex = l - 1;
if (current.length < 4) {
switch (l) {
case 2:
var first = current[0];
var second = current[1];
first.setNextEvent(second);
second.setPreviousEvent(first);
break;
case 3:
var first = current[0];
var second = current[1];
var third = current[2];
first.setNextEvent(second);
second.setPreviousEvent(first);
second.setNextEvent(third);
third.setPreviousEvent(second);
break;
default:
break;
}
} else {
// do something
}
},
populateTopicsDropDown: function(listTopics) {
//console.log("inside topics drop");
//console.log(listTopics);
var topicsDropDown = document.getElementById("eventTopicListBox");
for(var index in listTopics) {
var op = document.createElement("option");
op.setAttribute("id", "dd" + index);
op.innerHTML = listTopics[index].Main_Topic;
topicsDropDown.appendChild(op);
}
},
populateTypesDropDown: function(listTypes) {
//console.log("inside types drodown");
//console.log(listTypes);
var typesDropDown = document.getElementById("eventTypeListBox");
for(var index2 in listTypes) {
var op2 = document.createElement("option");
op2.setAttribute("id", "dd2" + index2);
op2.innerHTML = listTypes[index2].Main_Type;
typesDropDown.appendChild(op2);
}
}
},
init: function() {
dom = this.domElements;
S = this.settings;
M = this.methods;
currentArray = S.eventObjArray;
buttonArray = S.buttonObjArray;
topicsArray = S.eventTopicsArray;
typesArray = S.eventTypesArray;
M.ajax(S.urlList.allURL, M.allCallBack);
//var tempList = currentArray;
//console.log("temp array length: " + tempList.length);
M.setAllEvents(currentArray);
M.testList(currentArray);
M.setEventNavigation(currentArray);
//M.setEventNavigation();
M.ajax(S.urlList.topicsURL, M.topicsCallBack);
M.ajax(S.urlList.typesURL, M.typesCallBack);
}
};
The problem you have is that currentArray gets its value asynchronously, which means you are calling setAllEvents too soon. At that moment the allCallBack function has not yet been executed. That happens only after the current running code has completed (until call stack becomes emtpy), and the ajax request triggers the callback.
So you should call setAllEvents and any other code that depends on currentArray only when the Ajax call has completed.
NB: The reason that it works in the console is that by the time you request the value from the console, the ajax call has already returned the response.
Without having looked at the rest of your code, and any other problems that it might have, this solves the issue you have:
init: function() {
dom = this.domElements;
S = this.settings;
M = this.methods;
currentArray = S.eventObjArray;
buttonArray = S.buttonObjArray;
topicsArray = S.eventTopicsArray;
typesArray = S.eventTypesArray;
M.ajax(S.urlList.allURL, function (j) {
// Note that all the rest of the code is moved in this call back
// function, so that it only executes when the Ajax response is
// available:
M.allCallBack(j);
//var tempList = currentArray;
//console.log("temp array length: " + tempList.length);
M.setAllEvents(currentArray);
M.testList(currentArray);
M.setEventNavigation(currentArray);
//M.setEventNavigation();
// Note that you will need to take care with the following asynchronous
// calls as well: their effect is only available when the Ajax
// callback is triggered:
M.ajax(S.urlList.topicsURL, M.topicsCallBack); //
M.ajax(S.urlList.typesURL, M.typesCallBack);
});
}

Get result of Parse.Query to anothet output variable, table. JAVASCRIPT and PARSE.COM

My function takes a single object from the database parse.com . I need send result of query to another variable. This is my function:
function pobierzOcene(id){
var ocena = new Ocena();
var q = new Parse.Query(Parse.Object.extend("Ocena"));
q.equalTo("objectId",id);
q.first({
success: function(result){
ocena.przedmiot = result.get("ocenaPrzedmiot").id;
ocena.data = result.createdAt;
ocena.waga = result.get("ocenWaga");
ocena.wartosc = result.get("ocenaWartosc");
return ocena;
},
error: function(e){
console.log(e.message());
return e.message();
}
});
}
I'm using "return ocena;" to do this. And in another function I have:
var o = pobierzOcene(oceny[j].id);
Why variable o is empty?
Because of the asynchronous nature of Parse query functions. Your function pobierzOcene reaches the end of its execution as it does not wait for your asynchronous query function q.first to finish. One way of doin it is to rewrite the function with Parse Promises:
function pobierzOcene(id){
var q = new Parse.Query("Ocena");
q.equalTo("objectId",id);
return q.first(); // return a promise to wait on
}
Call the function like this:
pobierzOcene(oceny[j].id).then( function(result) {
// process the results here instead
}, function(error) {
console.log(error); // error
});
I have a second problem with function .than i think..
Function takes a list of students and theirs attributes (name, surname, id and degrees). In base:
Student 0 has 3 degrees.
Student 1 hasn't degrees.
Student 2 has 4 degrees.
My function:
function DownloadList(){
var query = new Parse.Query(Parse.Object.extend("Groups"));
var Students;
var degree = new Degree();
query.equalTo("objectId",GroupId);
query.include("listStudents");
query.include("degree");
query.include("StudentDegrees");
query.include("SubjectDegree");
query.first({
success:function(res){
var degree = res.get("Degree").id;
var listOfStudents = res.get("listStudents");
Students = new Array();
for (var i=0; i<listOfStudents.length ; i++){
student = new Student();
student.name = listOfStudents[i].get("name");
student.surname = listOfStudents[i].get("surname");
student.index = listOfStudents[i].get("index");
student.objectId = listOfStudents[i].id;
var degrees = listOfStudents[i].get("StudentDegrees");
for (var j in degrees){
takeADegree(degrees[j].id).then(function(result){
var degree = new Degree();
degree.subject = result.get("SubjectDegree");
if (subject == degree.subject.id){
degree.date = result.createdAt;
degree.weight = result.get("degreeWeight");
degree.value = result.get("degreeValue");
student.AddDegree(degree);
}
},
function(error){
console.log(error);
});
}
Students.push(student);
}
console.log(Students);
},
error:function(err){
console.log(err.message);
}
});
}
And function takeADegree(id):
function takeADegree(id){
var q = new Parse.Query("Degree");
q.equalTo("objectId",id);
return q.first();
}
In the picture the result of the function:
Fields that indicated by the arrows (oceny-degrees) should look like:
Student 0 = oceny: Array[3],
Student 1 = oceny: Array[0],
Student 2 = oceny: Array[4].
Where is the problem now?

Element Array Access within a Deferred Object

How would I access the values of 'timestamp' and 'usage' in the following example,
function executeReadingsQuery(query, postQueryProcessing) {
var d = new $.Deferred();
var processing = function(tx, results) {
var result = [];
var len = results.rows.length;
for ( var i = 0; i < len; i++) {
result.push({
"timestamp" : moment(results.rows.item(i).timeStamp),
"usage" : results.rows.item(i).usage
});
}
if (postQueryProcessing) {
result = postQueryProcessing(result);
}
d.resolve(result);
};
executeQuery(query, processing);
return d;
}
A function that builds a query string will subsequently call the above function,
function getReadingsInternal(noOfReadings, postQueryProcessing) {
var query = "SELECT * from usage ORDER BY timestamp DESC limit " + noOfReadings.toString();
return executeReadingsQuery(query, postQueryProcessing);
}
And then there is another function that exposes the entire functionality globally,
getReadings : function(noOfReadings) {
return getReadingsInternal(noOfReadings);
}
The original function (the first one listed) is within a Variable called WNDatabase
So I can access the function with a call that looks like this
WNDatabase.getReadings(30)
But I would like to be able to also globally access the values of timestamp and usage which populate the result[] array of the deferred object.
It seems that it is not possible to do something like this
$.when(WNDatabase.getReadings(30)).done(function() {
for(var i=0; i<7; i++){
console.log(this[i].usage);
}
});
So what would one do in this event?

Categories

Resources