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/
Related
This question already has answers here:
How can I execute array of promises in sequential order?
(9 answers)
Closed 5 years ago.
$scope.SaveAuditItems = function (audit) {
var isError = false;
$scope.isProcessing = true;
var defer = $q.defer();
var promises = [];
for (var siteCounter = 0; siteCounter < audit.SessionSiteList.length; siteCounter++) {
for (var itemCounter = 0; itemCounter < audit.SessionSiteList[siteCounter].AuditItemList.length; itemCounter++) {
var item = audit.SessionSiteList[siteCounter].AuditItemList[itemCounter];
item.TotalItems = audit.SessionSiteList[siteCounter].AuditItemList.length;
item.CurrentItem = itemCounter;
promises.push($scope.submitaudit(item));
}
};
//It appears to be running all the promises in the array in parrallel then running CloseAudit.
$q.all(promises).then(
function (response) {
$scope.CloseAudit(audit.AuditSessionId).then(function () {
},
function(){
alert("done");
},
function(){
alert("done");
}
);
}).catch(function (exception) {
$scope.Loading("Could not submit audit session #'+ audit.AuditSessionId +' , please try again.", 2000);
});
}
How do make the promises run in consecutive order? It causes race conditions on the server data is being submitted to? This angular 1 code.
How do i use then when i do not know how many promises i will be running in sequence? All other answers use then but are always predefined count of promises. I cannot have a then within a then for ever..i can't seem to comprehend how i do this.
-------------------------------------edit 2---------------------------------------------
$scope.SaveAuditItems = function (audit) {
$ionicLoading.show({
template: '<i class="icon ion-loading-c"></i>Please wait..Sending Item ( 1 Of ' + $scope.AuditItemCounter + ' )',
}).then(function () {});
var isError = false;
$scope.isProcessing = true;
var defer = $q.defer();
var promises = [];
for (var siteCounter = 0; siteCounter < audit.SessionSiteList.length; siteCounter++) {
for (var itemCounter = 0; itemCounter < audit.SessionSiteList[siteCounter].AuditItemList.length; itemCounter++) {
var item = audit.SessionSiteList[siteCounter].AuditItemList[itemCounter];
item.TotalItems = audit.SessionSiteList[siteCounter].AuditItemList.length;
item.CurrentItem = itemCounter;
// $scope.Loading('Sending Item ( ' + item.CurrentItem + ' Of ' + item.TotalItems + ' )..', 0).then(function () {
promises.push($scope.submitaudit(item));
ConsoleLogger.AddLog('Sent Item ( ' + item.CurrentItem + ' Of ' + item.TotalItems + ' )');
//Loading.show({ template: '<i class="icon ion-loading-c"></i>Sending Item ' + item.CurrentItem + ' of ' + item.TotalItems + ' ', }).then(function () { });
$scope.Loading('Sent item ( ' + item.CurrentItem + ' Of ' + item.TotalItems + ' )', 0).then(function () {});
}
}
var all = promises.reduce(function (cur, next) {
return cur.then(next);
}, Promise.resolve(true));
all.then(function (a) {
$ionicLoading.show({
template: '<i class="icon ion-loading-c"></i>Finalising your audit..'
}).then(function () {
$scope.CloseAudit(audit.AuditSessionId).then(function () {
ConsoleLogger.AddLog('Audit submitted successfully.');
});
},
function () {
alert("doneeee");
},
function () {
alert("done");
}
);
});
You can see in the timing that the promises are not running in sequence as expected? What am i missing? I made the promises with a timeout of 7 seconds..
The closeaudit should have run after all the promises had returned but not happening for me!
GOT IT WORKING BY CHNAGING
promises.push($scope.submitaudit(item));
TO
promises.push(function(){return $scope.submitaudit(item)});
Here is a quick example how to do it. Reduce all promises from the array one by one, the reduce function will chain then() of each promise. It will start with first after the first has resolved it will call second and so on ... to the end ;].
var promises = [
function() { return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log('resolve promise 1 after 3sec');
resolve('promise 1');
}, 3000)
})},
function() { return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log('resolve promise 2 after 1.5sec');
resolve('promise 2');
}, 1500)
})},
function() { return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log('resolve promise 3 after 2sec');
resolve('promise 3');
}, 2000);
})}];
var all = promises.reduce(function(cur, next) {
return cur.then(next);
}, Promise.resolve(true));
all.then(function(a) {
console.log('all are done!');
});
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
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()">
I want to return a value inside a setInterval. I just want to execute something with time interval and here's what I've tried:
function git(limit) {
var i = 0;
var git = setInterval(function () {
console.log(i);
if (i === limit - 1) {
clearInterval(git);
return 'done';
}
i++;
}, 800);
}
var x = git(5);
console.log(x);
And it's not working.
Is there any other way?
What I'm going to do with this is to do an animation for specific time interval. Then when i reached the limit (ex. 5x blink by $().fadeOut().fadeIn()), I want to return a value.
This is the application:
function func_a(limit) {
var i = 0;
var defer = $.Deferred();
var x = setInterval(function () {
$('#output').append('A Running Function ' + i + '<br />');
if (i == limit) {
$('#output').append('A Done Function A:' + i + '<br /><br />');
clearInterval(x);
defer.resolve('B');
}
i++;
}, 500);
return defer;
}
function func_b(limit) {
var c = 0;
var defer = $.Deferred();
var y = setInterval(function () {
$('#output').append('B Running Function ' + c + '<br />');
if (c == limit) {
$('#output').append('B Done Function B:' + c + '<br /><br />');
clearInterval(y);
defer.resolve('A');
}
c++;
}, 500);
return defer;
}
func_a(3).then( func_b(5) ).then( func_a(2) );
This is not functioning well, it should print A,A,A,Done A,B,B,B,B,B,Done B,A,A,Done A but here it is scrambled and seems the defer runs all function not one after the other but simultaneously. That's why I asked this question because I want to return return defer; inside my if...
if (i == limit) {
$('#output').append('A Done Function A:' + i + '<br /><br />');
clearInterval(x);
defer.resolve('B');
// planning to put return here instead below but this is not working
return defer;
}
Do you expect it to wait until the interval ends? That would be a real pain for the runtime, you would block the whole page. Lots of thing in JS are asynchronous these days so you have to use callback, promise or something like that:
function git(limit, callback) {
var i = 0;
var git = setInterval(function () {
console.log(i);
if (i === limit - 1) {
clearInterval(git);
callback('done');
}
i++;
}, 800);
}
git(5, function (x) {
console.log(x);
});
Using a promise it would look like this:
function git(limit, callback) {
var i = 0;
return new Promise(function (resolve) {
var git = setInterval(function () {
console.log(i);
if (i === limit - 1) {
clearInterval(git);
resolve('done');
}
i++;
}, 800);
});
}
git(5)
.then(function (x) {
console.log(x);
return new Promise(function (resolve) {
setTimeout(function () { resolve("hello"); }, 1000);
});
})
.then(function (y) {
console.log(y); // "hello" after 1000 milliseconds
});
Edit: Added pseudo-example for promise creation
Edit 2: Using two promises
Edit 3: Fix promise.resolve
Try to get a callback to your git function.
function git(limit,callback) {
var i = 0;
var git = setInterval(function () {
console.log(i);
if (i === limit - 1) {
clearInterval(git);
callback('done') // now call the callback function with 'done'
}
i++;
}, 800);
}
var x = git(5,console.log); // you passed the function you want to execute in second paramenter
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.