I have a an issue where it seems as though my controller is not waiting on the http.get '.then'. I am getting data properly back but it seems as though another function is processing before the data is retrieved. I've gone through many posts and have tried many of the things mentioned in those posts, but it doesn't seem to help. I am using PHP to retrieve the data.
I have a HTML file that calls two functions (I had tried with one, but when that didn't work, I tried splitting up the functionality).
HTML of the calls
<form editable-form name="editableForm" onaftersave="fetch();updateDetailsData()" >
Controller functions
$scope.fetch = function() {
$http.get("api/checkSave/"+ JSON.stringify($scope.programDetails))
.then(function(data) {
$scope.okToSave = data.data.save;
$scope.missFields = data.data.fields;
console.log($scope.okToSave); // line #194
console.log($scope.missFields); // line #195
});
}
$scope.updateDetailsData = function(){
console.log($scope.okToSave); // line #202
}
What displays in the console shows:
undefined // line 202
false // line 194 - correct data
Object // line 195 - correct data
As you can see, it appears to be processing the function updateDetailsData before the fetch function finishes. I thought the then should make processing wait until the get is finished - the promise returned.
I need to do some processing in the updateDetailsData function based on the values in the $scope variables but when it gets there they are undefined.
Can someone help? I'm sure it is something little that I am missing, but I think I have tried just about all solutions provided on these forums and still end up with the same results.
The problem comes from: onaftersave="fetch(); updateDetailsData()".
The update function executes as soon as fetch returns, not as soon as fetch is resolved.
Rework your function a bit:
function fetch () {
return $http.get("api/checkSave/"+ JSON.stringify($scope.programDetails))
.then(function(data) {
// ...
});
}
$scope.fetchAndUpdate = function () {
fetch().then(updateDetailsData);
}
In the template:
<form editable-form name="editableForm" onaftersave="fetchAndUpdate()">
You're using promises, so you have to wire into them, they aren't blocking:
<form editable-form name="editableForm" onaftersave="fetch().then(updateDetailsData)" >
Controller functions
$scope.fetch = function() {
return $http.get("api/checkSave/"+ JSON.stringify($scope.programDetails))
.then(function(data) {
$scope.okToSave = data.data.save;
$scope.missFields = data.data.fields;
console.log($scope.okToSave); // line #194
console.log($scope.missFields); // line #195
});
}
$scope.updateDetailsData = function(){
console.log($scope.okToSave); // line #202
}
Your fetch function is calling $http.get which makes an asynchronous call to your server. What that means is that the call will return right away, it won't block (i.e. stop the code from executing) while it waits for the server to respond. That is why you provide a callback using the .then function.
So taken the way you have currently written it, it is working as designed. If you want to have updateDetailsData function be executed after your then code then you have to either put it inside of the then or chain the functions together like Michael P. Bazos or Matthew Berg suggested.
Related
I'd like to check an image to see if the resource is available, before displaying it. I found a good way to do that in AngularJS here: Angular js - isImage( ) - check if it's image by url
But every time I try to implement it, an infinite loop is triggered, even if I reduce the function to its simplest form in a codepen : https://codepen.io/anon/pen/mBwgbE
test image function (js)
$scope.testImage = function(src) {
console.log('function triggered');
Utils.isImage(src).then(function(result) {
return "result";
});
};
Usage (html)
<h3>Image link broken<h3>
<p>{{testImage('anylink')}}</p>
<h3>Image link OK<h3>
<p>{{testImage('http://lorempixel.com/400/200/sports/1/')}}</p>
Can anyone explain this behaviour to me, and help me fix it?
Angular runs the digest loop, and interprets your template. It sees {{testImage('anylink')}} and calls into it. This calls into Utils.isImage, which creates a promise. The promise is returned to testImage, but testImage itself doens't return anything, so the template shows nothing.
A little later, the promise resolves. Angular sees this, so it runs the digest loop and interprets your template. It sees {{testImage('anylink')}} and calls into it. This calls into Utils.isImages, which creates a prom... oh crap, we're in an loop. It's going to call isImage, which creates a promise, and then when that promise resolves, it interprets the template again and calls isImage, starting all over.
Instead, i would recommend that when your controller loads, you create the promises right then, and when they resolve, you stick whatever values you need from them onto the controller as concrete values. Something like this:
function myController($scope, Utils) {
$scope.anyLink = null;
$scope.sportsLink = null;
Utils.isImage('anyLink')
.then(function (result) { $scope.anyLink = result });
Utils.isImage('http://lorempixel.com/400/200/sports/1/')
.then(function (result) { $scope.sportsLink = result });
$scope.heading = "My Controller";
}
And then on your template, interact with $scope.anyLink or $scope.sportsLink
$scope.testImage is automatically watched by angular to see change of testImage.
As a result you can stop infinite loop by using $scope.cache variable.
$scope.testImage = function(src) {
console.log('function triggered');
if($scope.cache[src] == "result")
return "result";
Utils.isImage(src).then(function(result) {
$scope.cache[src] = "result";
return "result";
});
};
It was tested on your codepen.
Here are my demo code:
doGet('../loaderDemo/1.lst');
doGet('../loaderDemo/2.lst');
doGet('../loaderDemo/3.lst');
doGet('../loaderDemo/4.lst');
doGet('../loaderDemo/5.lst');
function doGet(filename) {
$.get(filename,function (data) {
console.log(data + filename);
});
}
the line "console.log(...)" may not be executed as the order of doGet(), the output contents is not as the order of 1.lst -> 2.lst -> 3.lst -> 4.lst -> 5.lst.
Actually the output order is just random in each execution.
how could I let it outputs in order?
Thanks for your help :-)
-------------Update-------------------
the ".lst" files are 3D models that I want to load. I just want to load the models in order so that I can render an animation properly. so which is the best solution in this case?
each ".lst" files includes the information of one frame. and in this demo,the outputs of "console.log()" must be in order as 1.lst -> 2.lst -> 3.lst -> 4.lst -> 5.lst so that I can handle rendering a frame animation.
jQuery $.get returns a Promise (of sorts)
So, with minimal rewrite, you can do as follows
doGet('../loaderDemo/1.lst')
.then(function() {
doGet('../loaderDemo/2.lst');
})
.then(function() {
doGet('../loaderDemo/3.lst');
})
.then(function() {
doGet('../loaderDemo/4.lst');
})
.then(function() {
doGet('../loaderDemo/5.lst');
});
function doGet(filename) {
// added return
return $.get(filename,function (data) {
console.log(data + filename);
});
}
If, however, the order of download completion is not important, but order of "processing" is - you can use jQuery.when to "wait" for all the downloads to complete, then process the results in the order required
$.when(doGet('../loaderDemo/1.lst'),
doGet('../loaderDemo/2.lst'),
doGet('../loaderDemo/3.lst'),
doGet('../loaderDemo/4.lst'),
doGet('../loaderDemo/5.lst')
)
.done(function(v1, v2, v3, v4, v5) {
[].forEach.call(arguments, function(arg) {}
console.log(arg.data, arg.filename);
})
});
function doGet(filename) {
return $.get(filename)
.then(function(data) {
// need this so we can access the filename and the data for each result
return {
data: data,
filename: filename
};
});
}
Welcome to the world of asynchronous programming. The best way to handle this is to call all 5 asynch functions at the same time, but delay the execution of the console log statements until they are all completed, and then run them in order.
(This is, of course, assuming that it's really important to run them in the same order all the time. An even better solution might be refactoring your code so that it doesn't matter which one completes first)
Here's an example for your problem.
Mostly, this is way faster than any of the sequential solutions posted because it's going to run 5 calls at the same time instead of 5 calls one after the other.
return $.when(
doGet('../loaderDemo/1.lst'),
doGet('../loaderDemo/2.lst'),
doGet('../loaderDemo/3.lst'),
doGet('../loaderDemo/4.lst'),
doGet('../loaderDemo/5.lst'),
).done( function(res1, res2, res3, res4, res5 ) {
console.log(res1),
console.log(res2),
console.log(res3),
console.log(res4),
console.log(res5),
});
(My previous edit used $q, but it turns out that jquery has a built-in thing that works almost the same)
I'm using the javascript sdk plugin for facebook to create a feed on my webpage.
The problem is that sometimes during load the feed gets unordered, even if i have setup a callback chain.
I think it gets unordered because sometimes the "second" async call gets processed faster than the "first" async call.
This is the first time i've been using callbacks, am i doing it right?
How can i solve the feed gets unordered if some calls finish faster than others?
The code below is only the relevant code and is under working status.
function initFeed(){
FB.api('/{id}/feed', function(response){
var feedArray = response.data;
$.each(feedArray, function(){
var $this = $(this)[0]; //Status Object for single Status in Feed
setStatus($this, processStatus); //processStatus is function defined below
});
});
}
function setStatus(statusObject, callbackProcessStatus){
FB.api("/{personId}?fields=id,link,name,picture",
function (response) {
var html = /* Generates html based from statusObject and response */
callbackProcessStatus(html);
});
}
function processStatus(html){
$('#fb-status-wrapper').append(html);
}
(was uncertain on the title of this post, please edit if you think it is not descriptive enough)
Best regards
This is a somewhat common problem with parallel async calls. The simplest solution requires promises. I recommend the Bluebird promise library, but most will do fine.
var fbApi = function(url){
return new Promise(function(resolve, reject){
FB.api(url, function(resp){ resolve(resp); });
});
}
function setStatus(statusObject){
return fbApi("/{personId}?fields=id,link,name,picture")
.then(function(response){
var html = ...;
return html;
});
}
function getFeedItemPromises(){
return fbApi("/{id}/feed").then(function(response){
return response.data.map(function(item){
});
});
}
Depending on your needs, initFeed could be one of these. The first renders the feed when all items are available, and the second renders it when each item is available, but enforces the order.
function initFeed(){
return Promise.all(getFeedItemPromises())
.then(function(itemsHtml){
// append all of the items at once
$('#fb-status-wrapper').append(itemsHtml.join("\n"));
});
}
Or this which ensures the order, but eagerly appends items to the feed, after all previous items have been added.
function initFeed(){
function renderItem(html){
$('#fb-status-wrapper').append(html);
}
// reduce can be used to chain promises in sequence
return getFeedItemPromises().reduce(function(p, nextPromise){
return p.then(function(){ return nextPromise })
.then(renderItem);
}, Promise.resolve())
}
An alternative would be to create a div for each item which acts as a placeholder, keep those in an array, and fill them in when each resolves. This works especially well if you know the height of the items beforehand, and fade them in when they load. From a UX perspective, this is the best in my opinion.
I would not recommend the above if you don't know the heights of items, as it'll cause headache inducing shifting of items as new ones are inserted.
Indeed you cannot rely on the order in which the requests will finish. The only way to be sure, is to only call the second one if the first one is done. But that will slow down the loading quite a lot.
Another possibility is to remember for each request which one it is, and insert the items in the right order (insert before a 'later' one, even if that one was received earlier).
I think the easiest way to do that, is to make placeholders for the items inside the each loop, so the placeholders are inserted in the right order. When the requests return, you just place the responses in the right placeholder.
It could look somewhat like this. 2 extra lines and a couple of tiny changes. I couldn't test this without the API, but I hope you get the idea.
function initFeed(){
FB.api('/{id}/feed', function(response){
var feedArray = response.data;
$.each(feedArray, function(index){
var $this = $(this)[0]; //Status Object for single Status in Feed
// Make a container per item inside the wrapper.
var $itemContainer = $('<div></div>');
$('#fb-status-wrapper').append($itemContainer);
// Pass the container to the api function.
setStatus($this, processStatus, $itemContainer); //processStatus is function defined below
});
});
}
function setStatus(statusObject, callbackProcessStatus, $container){
FB.api("/{personId}?fields=id,link,name,picture",
function (response) {
var html = /* Generates html based from statusObject and response */
// Pass the item place holder/container to the processing procedure.
callbackProcessStatus(html, $container);
});
}
function processStatus(html, $container){
$container.append(html);
}
I have a chain of events set in .then statements. I wish I understood a way to use .when() in this case. This function is called on a ngn-click. The issue is that I have to click twice for it to go through. The $rootScope.csa has data going into the function that is used in the .then( functions ). I when in inspect in the chrome debugger the and step through everything works fine I believe it is because the debugger is slowing down the application and allowing it to keep up with its self. Other wise when I go through with out the debugger it goes so fast that it takes two clicks for $rootScope.csa.section.data to be populated for the next function to work as expected. The first two then statement functions are services that are wrapped in a promise and $timeout on there end and the $timeouts do not seem to be delaying the process. I have looked over q.derer() many times but cannot wrap my head around how it would be implemented in this case. Any help or information to get to the needs that I am looking for would ne appreciated.
audit.LockData('section', $scope.csa.ID, user.ID, $scope.csa.Revision)
.then($rootScope.csa = audit.RecordsCheck($rootScope.csa)) // gets data and pupulates it to the $rootscope.csa.section.data
.then($rootScope.csa = audit.GetInstance($rootScope.csa), function(){ return $rootScope.csa}) // gets ID(s) from $rootScope.csa.section.data.instances.ID(s) and populates them to the $rootScope.csa.section.instances
.then(function() {if($rootScope.csa.section.data) $location.path('/Update')})
.then(function() {if($rootScope.csa.section.data) $rootScope.$broadcast('root_updated')
});
You always need to pass a callback function to then, not some call result (even if it is a promise). I have not wrapped my head around what these functions do, but try
audit.LockData('section', $scope.csa.ID, user.ID, $scope.csa.Revision)
.then(function(lockResult) {
return audit.RecordsCheck($rootScope.csa)) // gets data and pupulates it to the $rootscope.csa.section.data
})
.then(function(checkResult) {
return audit.GetInstance($rootScope.csa) // gets ID(s) from $rootScope.csa.section.data.instances.ID(s) and populates them to the $rootScope.csa.section.instances
})
.then(function(instance) {
if ($rootScope.csa.section.data) {
$location.path('/Update')
$rootScope.$broadcast('root_updated')
}
});
I'm attempting to get the length of the array of a simple angularFireCollection and can't seem to:
var stf = new FireBase("http://myfirebase-app.firebaseio.com/staff");
function staffListCtrl($scope, angularFireCollection){
$scope.staff = angularFireCollection(stf);
console.log($scope.staff.length);
}
The output in the console says:
0
Which I know is incorrect. It should be returning somewhere around 5 as the length (see screenshot below for the output of $scope.staff.
Any help is a appreciated as I can't seem to get past this absolutely, utterly simple JS task.
In this case, the correct way to print the number of retrieved elements within the callback would be following:
var stf = new FireBase("http://myfirebase-app.firebaseio.com/staff");
function staffListCtrl($scope, angularFireCollection) {
$scope.staff = angularFireCollection(stf, function() {
console.log(stf.numChildren());
});
}
The reason for this is that the initial callback function is called before actual assignment to $scope.staff takes place. So within the callback function you can access only the Firebase DataSnapshot object stf. Hope this helps.
You're trying to access the length immediately after calling angularFireCollection, but the actual data is retrieved over the network and therefore it takes a little while for the array to be updated. You can pass a function as an argument to angularFireCollection to be notified about when the initial data is loaded, like so:
var stf = new FireBase("http://myfirebase-app.firebaseio.com/staff");
function staffListCtrl($scope, angularFireCollection) {
$scope.staff = angularFireCollection(stf, function() {
console.log($scope.staff.length);
});
}
ah, I see it in angularfire.js. Line 298 wraps adding an item in a $timeout. That's causing the initalCb() to get called before the data has been added to the collection. I pulled the $timeout and it worked. However, then I had to call $scope.$apply() to reflect the added items. I ended up passing scope into angularFireCollection to be sure $apply() gets called. No idea what else I broke by pulling the timeout.
this is a view of the issue: plunkr
EDIT:
as far as I can tell and showed in the plunkr, this doesn't work.
$scope.staff = angularFireCollection(stf, function() {
console.log($scope.staff.length);
});
and while pulling the $timeout from angularfire.js did fix that particular issue, it caused all kinds of other headaches with databinding(as expected), so I put it back. It seems the way to go is to use the snapshot that is passed in the callback. I can't find a lot of documentation on it, but here's what worked for me:
$scope.staff = angularFireCollection(stf, function(snapshot) {
angular.forEach(snapshot, function(item){
//will let you iterate through the data in the snapshot
});
});