Why is setTimeout preventing recursive $http.get call - javascript

I have looked both here on SO and Googled it but I am struggling to find any solution to this.
I have the below function and when I don't use the setTimeout() function and just call my polling function it works as expected. But when I try to wrap my polling function inside a setTimeout() it works once and then doesn't get called again unless the page is refreshed, I have already included a timestamp in the GET request to prevent using a cached response so I don't think this is the issue. I have also checked and this behaviour happens in IE9, Firefox and Chrome.
$scope.progressPolling = function () {
var time = new Date().toString();
console.log("time :" + time);
$http.get('pollprogress?time=' + time + '&id=' + $scope.jobID)
.success(function (data, status, headers, config) {
var percent = data.percentage;
if (parseInt($scope.progress) < 100) {
if (percent <= 100) {
$scope.progress = percent;
}
setTimeout(function() {
if (parseInt($scope.progress) < 100) {
temp = parseInt($scope.progress) + 1;
$scope.progressPolling();
};
}, 5000);
}
})
.error(function (data, status, headers, config) {
console.log("Error updating Progress: " + data);
});
}

Try to change it to $timeout
$scope.progressPolling = function () {
var time = new Date().toString();
console.log("time :" + time);
var stop;
$http.get('pollprogress?time=' + time + '&id=' + $scope.jobID)
.success(function (data, status, headers, config) {
var percent = data.percentage;
if (parseInt($scope.progress) < 100) {
if (percent <= 100) {
$scope.progress = percent;
}
stop = $timeout(function() {
if (parseInt($scope.progress) < 100) {
temp = parseInt($scope.progress) + 1;
$scope.progressPolling();
}
else{
$timeout.cancel(stop);
}
}, 5000);
}
})
.error(function (data, status, headers, config) {
console.log("Error updating Progress: " + data);
});
}
As a side note, create factory:
myModule.factory('delay', ['$q', '$timeout', function ($q, $timeout) {
return {
start: function () {
var deferred = $q.defer();
$timeout(deferred.resolve, 5000);
return deferred.promise;
}
};
}]);
After you can call it like:
$q.all([delay.start(), /*your method */]);`

The reason setTimeout appears to not work is because the callback function is executing outside of a digest cycle, so the bindings aren't updated. The $timeout service wraps the call with $scope.$apply(...) so the UI is updated. You could do it yourself in the setTimeout callback.

Related

Understanding chaining of sequential asynchronous operations using jquery deferred and then

I have been trying to wrap my head around jquery deferred and then functions. As I gather from jQuery then documentation, the then function sends the return value of the callback to the next then handler if they are so chained. Given that, why is my code not working as expected?
function log(message) {
var d = new Date();
$('#output').append('<div>' + d.getSeconds() + '.' + d.getMilliseconds() + ': ' + message + '</div>');
}
function asyncWait(millis) {
var dfd = $.Deferred();
setTimeout(function () {
var d = new Date();
log('done waiting for ' + millis + 'ms');
dfd.resolve(millis);
}, millis);
return dfd.promise();
}
function startTest0() {
return asyncWait(1000).then(asyncWait).then(asyncWait).then(asyncWait).done(function () {
log('all done, 4 times');
});
}
function startTest() {
asyncWait(500).then(function () {
return asyncwait(1000);
}).then(function () {
return asyncWait(1500);
}).then(function () {
return asyncWait(2000);
}).done(function () {
log('all done');
});
}
log('welcome');
log('starting test ...');
startTest0().done(function() { log('starting the second test'); startTest(); });
JS Fiddle here: Sample code. I was expecting a similar behavior in both tests but something eludes me. What am I missing?
Thanks in advance!
EDIT: See an updated DEMO where I am trying to chain the async operations to start after the previous one is done.
Except for one typo (asyncwait instead of asyncWait) your code works. Check below.
function log(message) {
var d = new Date();
$('#output').append('<div>' + d.getSeconds() + '.' + d.getMilliseconds() + ': ' + message + '</div>');
}
function asyncWait(millis) {
var dfd = $.Deferred();
setTimeout(function () {
var d = new Date();
log('done waiting for ' + millis + 'ms');
dfd.resolve(millis);
}, millis);
return dfd.promise();
}
function startTest0() {
return asyncWait(1000).then(asyncWait).then(asyncWait).then(asyncWait).done(function () {
log('all done, 4 times');
});
}
function startTest() {
asyncWait(500).then(function () {
return asyncWait(1000);
}).then(function () {
return asyncWait(1500);
}).then(function () {
return asyncWait(2000);
}).done(function () {
log('all done');
});
}
log('welcome');
log('starting test ...');
startTest0().done(function() { log('starting the second test'); startTest(); });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="output"></div>
Lesson to learn: Put any JS code through jshint before and after you fix bugs.
As i can see here, you are calling startTest0 function returning its promise object and calling then callback without returning new times into next then callback. I modified your startTest() into this :
function startTest() {
return asyncWait(500).then(function () {
asyncWait(1000);
return 1500; // here we pass to the next then
}).then(function (ms) { // ms here we got 1500
asyncWait(ms);
return 2000; // here we pass to the next then
}).then(function (ms) { // ms here we got 2000
asyncWait(ms)
return asyncWait(2500);
}).done(function () {
log('all done');
});
}
DEMO

Trying to get interval function to work with my factory

I create a factory where I put my http request for adding, retrieving and deleting tasks.
Now when I add a task you don't see the visual change. I have to refresh the browser. Now to fix this I figured I would write a function to check the length of the old array against the length of the new one and set an interval off 1 minutes on it to make this change and then pull in the tasks. I am able to log my length change of the array but nothing happens visually (still have to refresh the browser).
If anybody could help me out here.
Part of my app.js code:
global vars -> defaultStart, defaultEnd, clickDate
zazzleApp.factory('TaskService', function ($http) {
var TaskService = {};
TaskService.taskList = [];
TaskService.getTasks = function(cb){
$http.get('api/task/all')
.success(function(dataFromServer){
for (var i = 0; i < dataFromServer.length; i++) {
TaskService.taskList[i] = dataFromServer[i];
};
//console.log('LOGGING GET_TASK ', TaskService.taskList);
if(cb){
cb(dataFromServer);
}else{
return dataFromServer;
}
//return dataFromServer;
})
.error(function(errorFromServer){
//something went wrong, process the error here
console.log("Error in getting the users from the server ", errorFromServer);
})
};
TaskService.addTask = function(pTask){
var newClickDate = clickDate;
console.log('LOGGGING NEW CLICK DATE = ', newClickDate);
var newEditId = editId;
//console.log('LOGGGING NEW edit id = ', newEditId);
var url;
if (newEditId) {
url = 'api/task/update/' + newEditId;
} else {
url = 'api/task/create';
}
//console.log("URL URL USA", url, newEditId, newClickDate);
defaultStart = new Date(newClickDate);
defaultStart = defaultStart.getFullYear() + "-" + (defaultStart.getMonth() + 1) + "-" + defaultStart.getDate();
defaultStart += " 00:00:00";
defaultEnd = new Date(newClickDate).addDays(1);
defaultEnd = defaultEnd.getFullYear() + "-" + (defaultEnd.getMonth() + 1) + "-" + defaultEnd.getDate();
defaultEnd += " 00:00:00";
console.log('LOGGING DEFAULT START AND DEFAULT END ' , defaultStart, defaultEnd);
pTask.color = $('#containerColorPicker').attr('ng-data-id');
// returns makes sure the promis is returned from the server
return $http.post(url, {
'name': pTask.project_name,
'project_id': pTask.project_type,
'location_id': pTask.location,
'estimate_time': pTask.estimate_time || 2,
'project_client_name': pTask.project_client_name,
'url': pTask.url,
'resource_link': pTask.resource_link,
'notes': pTask.notes,
'start_time': pTask.start_time || defaultStart,
'end_time': pTask.end_time || defaultEnd,
/*'start_time': defaultStart,
'end_time': defaultEnd,*/
'color': pTask.color
}, {
headers: {
"Content-Type": "text/plain"
}
})
.success(function(data, status, headers, config) {
console.log(data);
TaskService.getTasks();
TaskService.taskList.push(data);//pushing the new task
//console.log("YYYYYYYYYYYYY -------->>>>>", defaultStart);
})
.error(function(data, status, headers, config) {
console.log("Failed to add the task to DB");
});
};
TaskService.deleteTask = function (){
var newEditId= editId;
$http.delete('api/task/delete/' + newEditId)
.success(function(dataFromServer){
console.log('logging edit id in delete taks func server ', newEditId);
var index;
for (var i = 0; i < TaskService.taskList.length; i++) {
if(TaskService.taskList[i]._id == newEditId){
//index = i;
console.log ("removing the element from the array, index: ", newEditId, i);
TaskService.taskList.splice(i,1);
}
};
/* if(editId !== -1){
console.log ("removing the element from the array, index: ", editId, index);
UserService.userList.splice(index,1);
}*/
console.log('TaskArray ', TaskService.taskList)
$('div[ng-data-id="'+ newEditId +'"]').remove();
})
.error(function(errorFromServer){
//something went wrong, process the error here
console.log("Error in deleting a user from the server");
})
};
return TaskService;
})
//START CONTROLLER
angular.module('zazzleToolPlannerApp')
.controller('CalendarCtrl', function ($scope, $mdDialog, $http, $rootScope, $timeout, User, Auth, UserService, TaskService) {
$scope.newTask = {};
$scope.newTask.project_name = "";
$scope.newTask.project_type = "";
$scope.newTask.location = "";
$scope.newTask.estimate_time = "";
$scope.newTask.project_client_name = "";
$scope.newTask.url = "";
$scope.newTask.resource_link = "";
$scope.newTask.notes = "";
$scope.newTask.color = "";
$scope.tasks = TaskService.taskList;
$scope.getTasksFromService = function () {
TaskService.getTasks(); //after this gets called, the data will be shown in the page automatically
}
$scope.getTasksFromService();
$scope.addTaskWithService = function () {
//note that you can process the promise right here (because of the return $http in the service)
TaskService.addTask($scope.newTask)
.success(function(data){
//here you can process the data or format it or do whatever you want with it
console.log("Controller: the task has been added");
$scope.tasks = [];// EMPTY THE ARRAY
$scope.tasks = TaskService.getTasks();
//console.log('Taskservice Controller ', $scope.updateGridDataAwesome);
})
.error(function(data){
//something went wrong
console.log("Controller: error in adding task");
});
}
$scope.deleteTaskWithService = function(){
TaskService.deleteTask();
}
TaskService.getTasks(function(data){
$scope.tasks = data;
});
var interval = setInterval(function(){
var oldLength = $scope.tasks.length;
TaskService.getTasks(function(data){
console.log('lengths', oldLength, data.length)
if(oldLength != data.length){
//$scope.tasks = data;
//TaskService.taskList.push(data);
$scope.tasks = TaskService.getTasks();
}
});
}, 6000)
This might be what you're looking for, by using $interval you can set an interval every x seconds:
$interval(function() {
// Something to be executed after 1min (60000ms)
}, 60000);
And then inject $interval into your factory:
zazzleApp.factory('TaskService', function ($http, $interval).....
Try this:
$http
.post("/api/pin", {})
.success(function(data) {
$scope.$apply(function() {
$scope.pins.push(data);
});
});
reference: https://stackoverflow.com/a/24836089/3330947
Update:
I did it like this:
Service (fetch all accounts):
.factory('accountService', function ($http) {
var accountObj = {
async: function () {
var promise = $http.get('account/').then(function (response) {
return response;
});
return promise;
}
};
return accountObj;
})
Controller:
//Call get accounts service and put all accounts in $scope.accounts
var getAccounts = function () {
accountService.async().then(function (d) {
$scope.accounts = d.data;}
}
//Create new account and update array of accounts
$scope.createAccount = function () {
$scope.data = {
'id' : 0,
'customerId' : $scope.outputCustomer[0].id,
'camsId' : $scope.outputCams[0].id,
'camsPin' : parseInt($scope.camsPin),
'username' : $scope.username,
'password' : $scope.password,
'email' : $scope.email,
'acfUsername' : $scope.acfUsername,
'accountActive' : $scope.accountActive,
'agentId' : $scope.outputAgent[0].id,
'freeswitchIds' : freeswitchIds
};
$http.post('account/save', $scope.data).success(
function (data, status) {
$scope.accounts.push(data);
}).error(function () {
});
};
My add function is in controller, i thin it can be redone to be service but this work soo
The problem may be in success of addTask. TaskService.getTaskis async. So the execute order will be: 1. http request in TaskService.getTasks 2. TaskService.taskList.push(data); 3. $http success callback.
.success(function(data, status, headers, config) {
console.log(data);
TaskService.getTasks();
TaskService.taskList.push(data);//pushing the new task
//console.log("YYYYYYYYYYYYY -------->>>>>", defaultStart);
})
In this order, step 2 is pointless. Because step 3 code may override TaskService.taskList
for (var i = 0; i < dataFromServer.length; i++) {
TaskService.taskList[i] = dataFromServer[i];
};
And another wrong place:
$scope.tasks = TaskService.getTasks();
this line code appears twice. But TaskService.getTasks() returns undefined because you don't put keyword return.
In addition, your practice to use promise is wrong. $http leverages promise specification. Don't use callback since you used promise. You may need read promise docs.

infinite scroll in angular directive

I coded the below directive for infinite scroll, my problem which I couldn't figure out why it just fire once when the directive is loaded, I need your advice on how to make my list infinite-scroll.
I'm using it to get data remotely and each time i'm calling it I add to the counter 25, so each time it would return more data.
Thanx,
angular.module('MyApp')
.controller('InboxCtrl', function($scope, InboxFactory) {
var counter = 0;
$scope.loadData = function() {
var promise = InboxFactory.getEvents(counter);
promise.then(function(result) {
$scope.events = result;
});
counter += 25;
};
});
angular.module('MyApp')
.factory('InboxFactory', function($http, $q) {
// Service logic
var defered = $q.defer();
function getUrl(count) {
return "api/inbox/get?request={'what':'Search','criteria':'inbox','criteriaId':null,'startTime':null,'endTime':null,'offset':" + count + ",'limit':25,'order':'event_time','direction':'DESC','source':''}";
}
function extract(result) {
return result.data.data;
}
// Public API here
return {
getEvents: function(count) {
$http.get(getUrl(count)).then(
function(result) {
defered.resolve(extract(result))
}, function(err) {
defered.reject(err);
}
);
return defered.promise;
}
};
});
angular.module('MyApp')
.directive('infiniteScroll', ['$timeout',
function(timeout) {
return {
link: function(scope, element, attr) {
var
lengthThreshold = attr.scrollThreshold || 50,
timeThreshold = attr.timeThreshold || 400,
handler = scope.$eval(attr.infiniteScroll),
promise = null,
lastRemaining = 9999;
lengthThreshold = parseInt(lengthThreshold, 10);
timeThreshold = parseInt(timeThreshold, 10);
if (!handler || !components.isFunction(handler)) {
handler = components.noop;
}
element.bind('scroll', function() {
var
remaining = element[0].scrollHeight - (element[0].clientHeight + element[0].scrollTop);
//if we have reached the threshold and we scroll down
if (remaining < lengthThreshold && (remaining - lastRemaining) < 0) {
//if there is already a timer running which has no expired yet we have to cancel it and restart the timer
if (promise !== null) {
timeout.cancel(promise);
}
promise = timeout(function() {
handler();
promise = null;
}, timeThreshold);
}
lastRemaining = remaining;
});
}
};
}
]);
<ul class="inbox-list" infinite-scroll="loadData()">
<li class="clearfix" ng-repeat="event in events">{{event}}</li>
</ul>
I Made some changes the more important is the use of ng-transclude and the creation of a new scope for the directive to pass the method and the parameters. You can have a look at the jsbind. Of course the data are hard coded so i could fake the behaviour.
<ul class="inbox-list" my-infinite-scroll composite-method="loadData()">

Call to JavaScript setTimeout not being called 10 times

i have a JavaScript/jQuery code function that is supposed to call itself up to ten times if there is no data available (determined by web service call). I have implemented the code but the logging inside the web service call indicates that it is called only 1 or 2 times. What is the error in this code?
function CallIsDataReady(input) {
var timer;
var count = 0;
$.ajax({
url: "https://www.blah.com/services/TestsService.svc/IsDataReady",
type: "GET",
contentType: "application/json; charset=utf-8",
data: input,
success: function (data) {
if (!data) {
setTimeout(function(inputInner) {
CallIsDataReady(inputInner);
count++;
if (count == 10) {
clearInterval(timer);
count = 0;
}
}, 1000);
} else {
console.log("data returned - returning true");
//Continue as data is ready
var tableView = $find("<%= RadGrid1.ClientID %>").get_masterTableView();
GetDataFromServer(0, tableView.get_pageSize());
}
},
error: function (jqXHR, textStatus, errThrown) {
console.log("AJAX call failed in CallIsDataReady");
console.log(errThrown);
}
});
}
EDIT: It should try up to ten times and then quit, not go on to the GetDataFromServer. It should return an error. How can I do that?
setTimeout is meant to trigger a function call once, and only once.
Repeat call to setTimeout from within your timeouted callback if you want this to work:
function CallIsDataReady(input) {
var timer;
var count = 0;
function callWebService(){
console.log('calling webservice');
$.ajax({
url: "https://www.blah.com/services/TestsService.svc/IsDataReady",
type: "GET",
contentType: "application/json; charset=utf-8",
data: input,
success: function (data) {
console.log('count = ' + count);
console.log('data = ' + data);
if (!data){
if(count < 10) {
count++;
setTimeout(callWebService, 1000);
} else {
count = 0;
}
}else{
console.log("data returned - returning true");
//Continue as data is ready
var tableView = $find("<%= RadGrid1.ClientID %>").get_masterTableView();
GetDataFromServer(0, tableView.get_pageSize());
}
},
error: function (jqXHR, textStatus, errThrown) {
console.log("AJAX call failed in CallIsDataReady");
console.log(errThrown);
}
});
};
callWebService();
}
count is being reset every time CallIsDataReady is called.
Replace:
function CallIsDataReady(input) {
var timer;
var count = 0;
With:
var count = 0;
function CallIsDataReady(input) { // You won't need the `timer` variable
This will set count to 0 before the first call of CallIsDataReady. Then, each call, the count will be incremented.
Now, to handle that counter properly, replace:
if (!data) {
setTimeout(function(inputInner) {
CallIsDataReady(inputInner);
count++;
if (count == 10) {
clearInterval(timer);
count = 0;
}
}, 1000);
With:
if (!data && count !== 10) {
setTimeout(function(input) {
CallIsDataReady(input);
count++;
}, 1000);
Now, I'm not sure what inputInner is supposed to be, so I replaced that with input. If you want a different variable to be passed to subsequent calls, you'll have to assign a value to inputInner.
In addition to making timer and count into global variables, I think you need to assign a value to inputInner when you execute it:
if (!data) {
setTimeout(function() {
function(inputInner) {
CallIsDataReady(inputInner);
count++;
if (count == 10) {
clearInterval(timer);
count = 0;
}
}(input);
}, 1000);
}
It looks like you are trying to use setTimeout instead of setInterval. setTimeout calls function only once after certain amount of time. setInterval will be calling your function in intervals until you call clearInterval.
http://www.w3schools.com/jsref/met_win_setinterval.asp
timer= setInterval(function(inputInner) {
CallIsDataReady(inputInner);
count++;
if (count == 10) {
clearInterval(timer);
count = 0;
}
}, 1000);

Can't get Angularjs promises working right

I know there's something I'm still missing when grasping promises. I created this jsfiddle to highlight my issue. I'm trying to count down and then go BOOM! Yet the boom is coming first and then the countdown. What am I doing wrong/missing?
http://jsfiddle.net/urAZ7/1/
HTML
<div ng-app="" ng-controller="Controller">
<pre>{{output}}</pre>
</div>
JS
function Controller($scope, $q) {
$scope.output = "Boom Goes the Dynamite in... "
$scope.countdown = 10;
var defer = $q.defer();
defer.promise
.then(function() {
var timer = setInterval(function() {
$scope.output+="\n " + $scope.countdown;
$scope.$apply();
if ($scope.countdown === 0) {
clearInterval(timer);
}
$scope.countdown--;
},1000);
return true
})
.then(function(data) {
$scope.output+="\n " + "BOOM!!!!!";
});
defer.resolve();
}
You should resolve promise only when your asynchronous operation is done. So you need to move resolve into setInterval:
function Controller($scope, $q) {
$scope.output = "Boom Goes the Dynamite in... "
$scope.countdown = 10;
var defer = $q.defer();
defer.promise.then(function() {
$scope.output += "\n " + "BOOM!!!!!";
});
var timer = setInterval(function() {
$scope.output += "\n " + $scope.countdown;
$scope.$apply();
if ($scope.countdown === 0) {
clearInterval(timer);
defer.resolve(); // <--- resolve here
}
$scope.countdown--;
}, 1000);
}
http://jsfiddle.net/urAZ7/4/

Categories