How to know when angular has finished deferring - javascript

Hi I am struggling to understand Angular deffering and promises.
I would like to know when all promises have been completed so i can hide a "loading" message.
So here is my code example:
$scope.buildTeam = function () {
$scope.Message = "loading...";
var deferred = $q.defer();
var promiseList = [];
c = $scope.teamModels[0];
for (index = 0; index < c.quantity; ++index) {
var assignment = new teamAssignment(c.assignmentTypeId, $scope.parentAssignment.AssignmentId, c.description);
promiseList.push(departmentService.addAssignment(assignment).then(function (result) {
//added lead/captain. Now insert visually on page
insertToScope($scope, result);
}));
}
}
$q.all(promiseList).then(function () {
deferred.resolve();
$scope.Message = "Loaded.";
});
}
The problem is Have is that $scope.Message shows "Loaded" well before the data has been inserted onto the page in the case of a large data pull.
Could it be that i also need to defer insertToScope?
InsertToScope simply reads:
function insertToScope($scope, a) {
//get root scope
var rs = angular.element("#ngRootContainer").scope();
rs.parentAssig.push(a);
}
And the department service looks like this:
departmentModule.factory('departmentService', function ($http, $q) {
return {
addAssignment: function(assignment){
var deferred = $q.defer();
$http.post('/Department/addAssignmentReturnAssignmentRow', assignment).success(deferred.resolve).error(deferred.reject);
return deferred.promise;
}
});
So my question is, what do i need to do to call a function only after all the promises are done?
Thank you in advance for your feedback.

$q.all should do what you need. If your code doesn't work, I see two options:
You pass in a promiseList that does not contain all promises you want to wait for. Debug your code and check that all promises are passed in. Make sure it's an array you pass as the first parameter.
The promises you pass in resolve before you expect them to resolve. If you log 'A' in insertToScope, and 'B' in the then of your $q.all, you should see
A
...
A
B
in the console.
Here is a simple plunkr that shows how to use $q.all(): http://plnkr.co/edit/9QH9Y0w7DG0WCouMuX82?p=preview

Related

Trouble with Angular $q.all()

I have a factory which is making 'n' number of $http request and to fulfill this I am using $q.all() , so let me show you my factory code.
app.factory('loadBooks',['$http','$q',function($http,$q){
var promises = [];
return {
getBooks : function(category,no){
for(var i =1; i<=no; i++){
var deferred = $q.defer();
console.log(i + 'request');
$http.get(`https://anapioficeandfire.com/api/${category}?page=${i}&pageSize=10`)
.then(function(response){
deferred.resolve(response);
console.log(response);
})
.catch(function(response){
deferred.reject(response);
})
promises.push(deferred);
}
return $q.all(promises);
}
}
}])
And this is my controller from where i am calling the factory function
app.controller('bookController',['$scope','loadBooks','$routeParams',function($scope,loadBooks,$routeParams){
$scope.count = $routeParams.no;
loadBooks.getBooks('books',2).then(function(data){
$scope.books = data;
})
.catch(function(response){
});
}]);
Now the problem is I am making 2 request from the getBooks method and the api is sending me data for both the request but for some reason the first request pushes no data inside the promise array but the second does. I dont know what i am doing wrong. The api is working fine because I can see the data coming in my network tab but data from first request is not getting pushed inside the promise array!
I would be thankful if you could tell me what the problem is. Btw I am new to $q.
You've fallen prey to an insidious bug called "closing over the loop variable". The value of the deferred variable changes in every iteration of the loop, so when this eventually executes:
deferred.resolve(response);
deferred is referring to the last value that the deferred variable took on.
On top of that, you're using the Explicit Promise Creation Antipattern, an antipattern that causes your promise code to be unnecessarily convoluted and error prone.
Solve both problems by using promises the way they're supposed to be used:
app.factory('loadBooks', ['$http', '$q', function($http, $q) {
return {
getBooks: function(category, no) {
var promises = [];
for (var i = 1; i <= no; i++) {
console.log(i + 'request');
promises.push(
$http.get(`https://anapioficeandfire.com/api/${category}?page=${i}&pageSize=10`)
);
}
return $q.all(promises);
}
}
}])

Javascript promise confusion for executing two functions in order

Right now I have funcA, funcB, arrayA and arrayB. In funcA, arrayB will populate itself by requesting some external information and the time for doing this is varied. I want it to execute funcB after arrayB.length == arrayA.length, and arrayB is a global variable that its content will be used in funcB. I assume that I need to use JQuery deferred and promise..so I tried this
var arrayB = [];
var hi = funcA();
hi.then(funcB());
funcA(){
var Dfd = $.Deferred();
arrayB.forEach(function(x, i){
some external retrieval;
if (arrayB.length == arrayA.length){
Dfd.resolve(arrayB);
}
})
return Dfd;
}
But this doesn't help. How should I change it?
arrayB.forEach won't do a thing. It's empty. Forget all this functions and deferred.
fetch('/mydata.json')
.then(function(response) {
//save your data from response to arrayB
//call your funcA
})
.catch((error) => {
console.log(error);
});
I don't know why you needed that:
if (arrayB.length == arrayA.length){
Dfd.resolve(arrayB);
}
But feel free to make a check before calling funcA.
I resolved the problem by doing this:
hi.done(function(){funcA()});

Promises for loop angular confused

I understand using promises in simple scenarios but currently really confused on how to implement something when using a for loop and some updates to local sqlite database.
Code is as follows
surveyDataLayer.getSurveysToUpload().then(function(surveys) {
var q = $q.defer();
for (var item in surveys) {
var survey = surveys[item];
// created as a closure so i can pass in the current item due to async process
(function(survey) {
ajaxserviceAPI.postSurvey(survey).then(function(response) {
//from response update local database
surveyDataLayer.setLocalSurveyServerId(survey, response.result).then(function() {
q.resolve; // resolve promise - tried only doing this when last record also
})
});
})(survey) //pass in current survey used to pass in item into closure
}
return q.promise;
}).then(function() {
alert('Done'); // This never gets run
});
Any help or assistance would be appreciated. I'm probably struggling on how best to do async calls within loop which does another async call to update and then continue once completed.
at least promises have got me out of callback hell.
Cheers
This answer will get you laid at JS conferences (no guarantees though)
surveyDataLayer.getSurveysToUpload().then(function(surveys) {
return Promise.all(Object.keys(surveys).map(function(key) {
var survey = surveys[key];
return ajaxserviceAPI.postSurvey(survey).then(function(response){
return surveyDataLayer.setLocalSurveyServerId(survey, response.result);
});
}));
}).then(function() {
alert('Done');
});
This should work (explanations in comments):
surveyDataLayer.getSurveysToUpload().then(function(surveys) {
// array to store promises
var promises = [];
for (var item in surveys) {
var survey = surveys[item];
// created as a closure so i can pass in the current item due to async process
(function(survey) {
var promise = ajaxserviceAPI.postSurvey(survey).then(function(response){
//returning this promise (I hope it's a promise) will replace the promise created by *then*
return surveyDataLayer.setLocalSurveyServerId(survey, response.result);
});
promises.push(promise);
})(survey); //pass in current survey used to pass in item into closure
}
// wait for all promises to resolve. If one fails nothing resolves.
return $q.all(promises);
}).then(function() {
alert('Done');
});
Awesome tutorial: http://ponyfoo.com/articles/es6-promises-in-depth
You basically want to wait for all of them to finish before resolving getSurveysToUpload, yes? In that case, you can return $q.all() in your getSurveysToUpload().then()
For example (not guaranteed working code, but you should get an idea):
surveyDataLayer.getSurveysToUpload().then(function(surveys) {
var promises = [];
// This type of loop will not work in older IEs, if that's of any consideration to you
for (var item in surveys) {
var survey = surveys[item];
promises.push(ajaxserviceAPI.postSurvey(survey));
}
var allPromise = $q.all(promises)
.then(function(responses) {
// Again, we want to wait for the completion of all setLocalSurveyServerId calls
var promises = [];
for (var index = 0; index < responses.length; index++) {
var response = responses[index];
promises.push(surveyDataLayer.setLocalSurveyServerId(survey, response.result));
}
return $q.all(promises);
});
return allPromise;
}).then(function() {
alert('Done'); // This never gets run
});

delay the execution of code till the completion of a function in Angularjs

In my Angularjs application, I have code as shown below. Initially $scope.changeChartTypeModelNames array is empty but inside $scope.doTimeConsumingTask, I am populating $scope.changeChartTypeModelNames with some values. But the problem is happening due to the time consuming tasks inside $scope.doTimeConsumingTask. Even before $scope.doTimeConsumingTask completes the next for loop is getting executed. So, I am always getting $scope.changeChartTypeModelNames length as Zero. Even though after the completion of $scope.doTimeConsumingTask the $scope.changeChartTypeModelNames array shows the correct values. Only after completion of $scope.doTimeConsumingTask, I want the next for loop to be executed. How can I achieve it? Consider that $scope.doTimeConsumingTask may or may not have Ajax calls.
$scope.$watch('isTopCarrierListClosed', function (isTopCarrierListClosed) {
if ($rootScope.isTopCarrierListClosed) {
$scope.doTimeConsumingTask($scope.dataSet);
for (var i = 0; i < $scope.changeChartTypeModelNames.length; i++) {
var dropDownName = $scope.changeChartTypeModelNames[i];
alert(dropDownName);
$scope.dropDownName = {};
}
}
}
$scope.doTimeConsumingTask = function(data){
...
...
}
So you should use de defer object and then to complete your job.
For example, in your doTimeConsumingTask method :
function doTimeConsumingTask(){
var def = $q.defer();
$http.get('/some_url')
.success(function(data){
def.resolve(data);
})
.error(function(data){
console.log('Error: ' + data);
def.reject('Failed to get todos');
});
return def.promise;
}
And in your main programm, you can use the doTimeConsumingTask as follow :
$scope.doTimeConsumingTask($scope.dataSet)
.then(function(data){
for (var i = 0; i < $scope.changeChartTypeModelNames.length; i++) {
var dropDownName = $scope.changeChartTypeModelNames[i];
alert(dropDownName);
$scope.dropDownName = {};
},
function(errorMsg){
console.log(errorMsg);
});
For information, here is the angular doc of $q.
Hope help.
Try looking into using $q from Angular docs, an implementation of promises/deferred objects inspired by Kris Kowal's Q.
Following the example from the documentation, (i used a different structure, but it should be clear) here's an implementation with comments:
(function() {
'use strict';
// your .module() and .controller()
angular
.module('app.carrier')
.controller('Carrier', Carrier);
// dependency injection
Carrier.$inject = ['$q'];
// the function with $q so we can use promises/deferred
function Carrier($q) {
var doTimeConsumingTask = function(data) {
// get it so we can use it
var deferred = $q.defer();
// demonstration flow of function calls
setTimeout(function() {
// seed some numbers between 1..10 to simulate
// resolve or reject by returning strings
var seed = Math.random() * (10 - 1) + 1;
if(seed < 5) {
deferred.resolve('OK');
} else {
deferred.reject('REJECTED');
}
}, 1000);
// now, this i promise you ( resolve or reject )
return deferred.promise;
};
// lets call your function, it returns a..
var promise = doTimeConsumingTask(someData);
// like english, if you have a promise then 'resolved' otherwise 'rejected'
promise.then(function(returnedString) {
alert('Success: ' + returnedString); // "OK"
}, function(reason) {
alert('Failed: ' + reason); // "REJECTED"
});
}
})();
This is 1 of 2 implementations (see docs).
Hope this helps.

Q require("asap") and getting related data in angularjs

I am using q.js for the first time with Angular.js and I have some code below where I am trying to get related info on a foreign key (each Team object has a GroupId foreign key that links to a Group object) :
new Team().$getAll().then(function (data)
{
angular.forEach(data.value, function(value, key) {
// We create our own promise to return
var deferred = $q.defer();
$http.get('http://mycloudapp.cloudapp.net/odata/Groups(' + value.GroupId + ')').then(function(group) {
angular.forEach(group, function(value, key) {
alert(value.GroupName);
});
//set the group here somehow?
$scope.teams = data.value;
// resolve the promise
deferred.resolve(group);
}, function getGroupError() { deferred.reject();
});
});
});
When I run this code I get the error :
ReferenceError: require is not defined
var asap = require("asap");
I have tried adding require.js and asap.js but that does not help, what do I need to do in this situation?
Also, I have not yet got to the stage where I actually get the group info and add it to $scope.teams, can anybody help me out with what syntax I would use to do this?
You will need to use the version of Q built for use in a web browser. The version checked in on Github is in development and unstable, so be sure to grab the latest release, 0.9.7. See “Getting Started” on https://github.com/kriskowal/q
First, with angular you will probably want to use the built in promise service and inject $q into your controller/service.
Second, it seems you want to resolve a bunch of promises in a function and the $q.all is the way to go. An example might be:
function myCtrl($q, $scope) {
function getKey(groupId) {
// We create our own promise to return
var deferred = $q.defer();
$http.get('http://mycloudapp.cloudapp.net/odata/Groups(' + groupId + ')').then(function(group) {
// resolve the promise
deferred.resolve(group);
}, function getGroupError() { deferred.reject();
});
return defferred.promise;
}
new Team().$getAll().then(function (data)
{
promiseArray = [];
angular.forEach(data.value, function(value, key) {
var p = getKey(value);
promiseArray.push(p);
});
$q.all(promiseArray).then(function (data) {
// data will have an array of promises returned after all complete.
$scope.teams = data; // just depends exactly what you are doing.
});
}
}
I posted a question about this prior that might help.

Categories