I need to execute a function after the recursive asynchronous ajax calls.
I have:
var tree = [1, 2 ,3, 4];
recursion(0);
function recursion(i) {
$.ajax('http://example.com/' + tree[i])
.done(function (data) {
// data is array
++i;
if (tree[i] !==undefined) {
$.each(data, function () {
recursion(i);
});
}
});
}
And I want after all calls when they are done to do:
recursion(0).done(function () {alert('All calls are done!')});
I know, I should use $.Deferred of JQuery, but ajax call return promise too.
I'm trying to use $.Deferred but I encountered a problem with loop in this place:
$.each(data, function () {
recursion(i);
});
Please, help me.
I'm trying to use $.Deferred
Good!
but I encountered a problem with loop in this place: $.each(data, recursion)
Each of the recursion(i) calls returns a promise, so when we have kicked them off we're left with a collection of promises. Now, we can use $.when to wait for all of them, and get back a promise for all of their results.
Now, we use then to chain this looped execution after the ajax call, so that we can return a promise for the eventual result of this recursion step.
function recursion(i, x) {
return $.ajax('http://example.com/' + tree[i] + x).then(function (data) {
if (++i < tree.length)
// get an array of promises and compose all of them
return $.when.apply($, $.map(data, function(d) {
return recursion(i, d);
}));
else // at the innermost level
return data; // get the actual results
}); // and return a promise for that
}
You would need to do something like this:
function recursion(i) {
return $.ajax('http://example.com/' + tree[i])
.then(function (data) {
// data is array
++i;
if (tree[i] !==undefined) {
// get an array of promises
var promises = $.map(data, function () {
return recursion(i);
});
// return the `when` promise from the `then` handler
// so the outer promise is resolved when the `when` promise is
return $.when.apply($, promises);
} else {
// no subsequent calls, resolve the deferred
}
});
}
Currently untested, but it at least gives you the idea. Basically you only resolve the deferred once all of the subsequent calls are resolved.
To make it simple, you could just run a function in your .done that checks the value of i and if it's equal to the length of tree (minus 1), then run your function.
Sorry, that doesn't cover the asynchronous nature...instead...create a variable that tracks the number of completed and compare to the number in the array. When equal, run your function.
Edit , jquery deferred , promise not actually needed to achieve requirement . See comment and link by Bergi , below.
Try (this pattern ; without jquery.deferred or promise's)
(function recursion() {
var tree = [1, 2 ,3, 4]
, results = []
, dfd = function(res) {
alert('All calls are done!');
console.log(res)
};
$.each(tree, function(k, v) {
$.ajax("http://example.com/" + v)
.done(function (data, status, jqxhr) {
// data is array
results.push([data, status, jqxhr.state()]);
if (results.length === tree.length) {
dfd(results);
}
});
});
}());
jsfiddle http://jsfiddle.net/guest271314/nvs0jb6m/
Related
I have a function processing an array, calling a function for each entry within the array with a Promise returned, and then finally returning that it's done.
var dealCardSelectableAI = function () {
var deferred = $.Deferred();
var deferredQueue = [];
_.forEach(model.galaxy.systems(), function (system, starIndex) {
if (model.canSelect(starIndex) && system.star.ai()) {
deferredQueue.push(
chooseCards({
inventory: inventory,
count: 1,
star: system.star,
galaxy: game.galaxy(),
addSlot: false,
}).then(function (result) {
system.star.cardList(result);
})
);
}
});
$.when.apply($, deferredQueue).then(function () {
deferred.resolve();
});
return deferred.promise();
};
dealCardSelectableAI().then( /* other stuff */ )
Most stuff I find on promises is to do with ES6 and Promises.all(), but I'm working in ES5 so am using jQuery.
My understanding is that due to deferredQueue being of unknown length $.when.apply() is the correct call here rather than $.when(), but regardless of which I use everything seems to operate as expected.
Have I set this up correctly? Is $.when.apply() the correct call and am I using it right?
The issue I am having is that I need to get a total count of a collection of objects, which has the form of a tree where each object can also contain deeply nested objects. The collection of data I start with already has one nested layer available, but to find out if there is a third or more nested layers in an object, an API call has to be made with the nested object's id, which returns the next nested object if it exists.
So, currently I have something like this:
function getCount(thread) {
var deferred = $q.defer();
var count = 0;
function getComment(comment) {
count++;
//if nested comments exist in data
if (comment.fld.nested) {
_.each(comment.fld.nested, function(x) {
getComment(x);
});
deferred.resolve(count);
} else if (comment.meta) {
//if not, load more from API with id
return PostService.getComment(comment.meta.id).then(function(comment){
if (comment.fld.nested) {
_.each(comment.fld.nested, function(x) {
return getComment(x);
});
}
return count;
});
}
return deferred.promise;
}
_.each(thread.fld.nested, function(x) {
return getComment(x);
});
return deferred.promise;
}
getCount(c).then(function(x) {
console.log('final count:', x);
});
Right now, I can get the count for all objects nested to 2nd level deep, but anything loaded from the API promise is not included in the count when I call the getCount().then() function. How do I make this wait until all promises are resolved so I can get the final count returned?
As Jaromanda X mentioned in the comments, you're having getComment return an asynchronous promise, but you're not waiting for the results.
One aspect hindering you is that you're using deferred-style promises; if you were to use then, you'll have the benefit of being able to return a promise in a then handler, which will cause the outer promise to wait on the inner promise.
// foo won't resolve until Promise Two resolves.
var foo = getPromiseOne().then(function(valueOne) {
return getPromiseTwo(valueOne);
});
You'll be waiting for a lot of promises in parallel, so I'm going to switch to Promise.all ($q.all) and Array.map (_.map) so it's clear what you're waiting for.
function getCount(thread) {
var count = 0;
function getComment(comment) {
count++;
//if nested comments exist in data
if (comment.fld.nested) {
return $q.all(_.map(comment.fld.nested, function(x) {
return getComment(x);
}));
} else if (comment.meta) {
//if not, load more from API with id
return PostService.getComment(comment.meta.id).then(function(comment){
if (comment.fld.nested) {
return $q.all(_.map(comment.fld.nested, function(x) {
return getComment(x);
}));
}
});
}
return; // Count matters, not return values, so returning undefined is fine.
}
// Wait for all nested promises to resolve, but ignore the array result
// and return the count instead.
return $q.all(_.map(thread.fld.nested, function(x) {
return getComment(x);
}).then(function() { return count; });
}
getCount(c).then(function(x) {
console.log('final count:', x);
});
Try removing the parameter "comment" from the API call then method:
From:
function(comment){}
To:
function(){}
I call a function that returns a list with ID that i want to use in chained call. Everything seems to work until i want to read all those objects that are returned.. Those are promises but i cannot find out why i cannot resolve them.
//Get bubbles and then it calls another function getBubbleMessage with result from previous and last getBubbleMessage returns an array of promises.
$scope.loadStartPage = function () {
$scope.getBubblesThatUserHasAccessTo().then($scope.getBubbleMessage).then(function (data) {
$log.info("INFO:: " + data);
$scope.bubblesWithMessages = data;
});
};
$scope.getBubblesThatUserHasAccessTo = function () {
var deferred = $q.defer();
BubblesService.getBubblesUserAccess().then(function (result) {
deferred.resolve(result);
});
return deferred.promise;
};
This function is gettings some things that we need to resolve messages connected to those id:s that above service is returning
$scope.getBubblesThatUserHasAccessTo = function () {
var deferred = $q.defer();
BubblesService.getBubblesUserAccess().then(function (result) {
deferred.resolve(result);
});
return deferred.promise;
};
This function get alls messages and returns promise objects - and these i cannot resolve??
$scope.getBubbleMessage = function (data) {
var deferred = $q.defer();
var promises = [];
angular.forEach(data, function (item) {
$log.info("DDD" + item.name);
var promise = BubblesService.getBubbleMessages(item.id, 0, 1);
promises.push(promise);
});
//return $q.all([promises]);
$q.all([promises]).then(function (result) {
$log.info(result);
return result[0];
});
};
Above function returns an array of 60 objects..
In the end i want to have a new object that i use in my ng-repeat on page. I really think this is something todo that im new to angular and promises.... but after a couple of hours of trying to fix this i really need help :)
$q.all takes an array of promise. Here, you are doing $q.all([myPromises]), which resolve instantly, because '[myPromise]' is an array and not a promise (you give an array parameter with first and only element is an array of promise when you should simply use the promise array. So [] and not [[]]). Second issue : you are not returning this promise in the parent function.
You should simply change the block
$q.all([promises]).then(function (result) {
$log.info(result);
return result[0];
});
with
return $q.all(promises);
Which will resolve with an array of resolved for each promise in the array.
When using jQuery promises sequentially, it is possible to chain them using then repeatedly:
e.g.
promise = promise.then(someoperation());
which also works inside a loop (very handy).
I have similar scenario where I needed to know when multiple parallel operations were completed, but not go through the coding overhead (e.g. added complexity) of creating an array of promises for the sole purpose of calling $.when.apply
After looking at my options, I came up with this pattern as an alternative:
promise = $.when(promise, anotherpromise);
To test it I came up with this test:
var p = $.Deferred().resolve().promise();
[1,2,3,4,5,6,7,8,9,10].forEach(function(i){
p = $.when(p, delay(i,i * 500));
});
p.then(function(){
log("All done");
});
JSFiddle: http://jsfiddle.net/TrueBlueAussie/0rh8Lhv4/1/
Which appears to work just fine, so I started applying it to other example on StackOverflow.
The next one I tried with this pattern was to fix the example from Pass in an array of Deferreds to $.when():
My code:
$("a").click(function () {
var promise = GetSomeDeferredStuff();
promise.then(function () {
$("div").append("<p>All done!</p>");
});
});
JSFiddle: http://jsfiddle.net/TrueBlueAussie/ts1dqwe3/1/
Q. For some reason this one never fires the final event. Can anyone spot the problem?
Update
Based on a comment from #Karl-André Gagnon, it seems the initial promise can just be undefined and still work. Much simpler:
e.g.
var p;
[1,2,3,4,5,6,7,8,9,10].forEach(function(i){
p = $.when(p, delay(i,i * 500));
});
p.then(function(){
log("All done");
});
Okay, it turns out this pattern does indeed work just fine, but you need to ensure the initial promise you chain to is already resolved:
function GetSomeDeferredStuff() {
var promise = $.Deferred().resolve().promise();
JSFiddle: http://jsfiddle.ne/TrueBlueAussie/ts1dqwe3/2/
In summary this pattern really is a simple alternative to creating an array just for use by $.when.apply.
Also, as pointed out by #Karl-André Gagnon, if you start with an undefined value, it does the same thing. Even better :)
function GetSomeDeferredStuff() {
var promise;
JSFiddle: http://jsfiddle.net/TrueBlueAussie/ts1dqwe3/4/
Updated
I have similar scenario where I needed to know when multiple parallel
operations were completed, but not go through the overhead of creating
an array of promises for the sole purpose of calling
loop not utilized ; only $.when() ; same pattern as utilized when implementing through $.map() . Additionally , added "random" delay to jsfiddle ajax request ; promise.then() should not be called until all parallel function calls at $.when() completed , next called , queueName queue empty .
function GetSomeDeferredStuff(elem, name, cycles) {
var el = (elem || {}),
queueName = (name || "q"),
len = (cycles || 5),
request = function () {
return $.ajax({
type: "POST",
url: '/echo/html/',
data: {
html: "<p>Task #" + $.now() + " complete.",
delay: Math.random() * 5
},
success: function (data) {
$("div").append(data);
}
})
}
return $(el).queue(queueName, function (next) {
return $.when(request()
, request()
, request()
, request()
, request())
.then(next)
}).dequeue(queueName).promise(queueName);
}
$(function () {
$("a").click(function () {
var promise = GetSomeDeferredStuff();
promise.then(function () {
$("div").append("<p>All done!</p>");
});
});
});
jsfiddle http://jsfiddle.net/ts1dqwe3/10/
When using jQuery promises sequentially, it is possible to chain them
using then repeatedly
...
which also works inside a loop (very handy).
Try utilizing .queue() .promise(queueName)
function GetSomeDeferredStuff(elem, name, cycles) {
// if no `elem`:`this` object passed , utilize empty object `{}`
var el = (elem || {})
// if no `name` passsed, utilize `String` `"q"`
, queueName = (name || "q")
// if no `cycles` how many functions passed to `.queue(queueName)`,
// pass `5` functions to `.queue(queueName)`
, len = (cycles || 5);
return $(el).queue(queueName
, $.map(Array(len), function (_, index) {
return function (next) {
return $.ajax({
type: "POST",
url: '/echo/html/',
data: {
html: "<p>Task #" + (1 + index) + " complete.",
delay: (index + 1) / 2
},
success: function (data) {
return $("div").append(data);
}
// call "next" function in `queue`
}).then(next)
}
// `.dequeue(queueName)` , return `queueName` jQuery promise object,
// when all functions in `queue(queueName)` called ;
// `.queue(queueName)` empty
})).dequeue(queueName).promise(queueName);
}
$(function () {
$("a").click(function () {
var promise = GetSomeDeferredStuff();
promise.then(function () {
// `this`:`elem` , or `{}`
$("div").append("<p>All done!</p>");
});
});
});
jsfiddle http://jsfiddle.net/ts1dqwe3/6/
I have a list of objects. The objects are passed to a deferred function. I want to call the function with the next object only after the previous call is resolved. Is there any way I can do this?
angular.forEach(objects, function (object) {
// wait for this to resolve and after that move to next object
doSomething(object);
});
Before ES2017 and async/await (see below for an option in ES2017), you can't use .forEach() if you want to wait for a promise because promises are not blocking. Javascript and promises just don't work that way.
You can chain multiple promises and make the promise infrastructure sequence them.
You can iterate manually and advance the iteration only when the previous promise finishes.
You can use a library like async or Bluebird that will sequence them for you.
There are lots of different alternatives, but .forEach() will not do it for you.
Here's an example of sequencing using chaining of promises with angular promises (assuming objects is an array):
objects.reduce(function(p, val) {
return p.then(function() {
return doSomething(val);
});
}, $q.when(true)).then(function(finalResult) {
// done here
}, function(err) {
// error here
});
And, using standard ES6 promises, this would be:
objects.reduce(function(p, val) {
return p.then(function() {
return doSomething(val);
});
}, Promise.resolve()).then(function(finalResult) {
// done here
}, function(err) {
// error here
});
Here's an example of manually sequencing (assuming objects is an array), though this does not report back completion or errors like the above option does:
function run(objects) {
var cntr = 0;
function next() {
if (cntr < objects.length) {
doSomething(objects[cntr++]).then(next);
}
}
next();
}
ES2017
In ES2017, the async/wait feature does allow you to "wait" for a promise to fulfill before continuing the loop iteration when using non-function based loops such as for or while:
async function someFunc() {
for (object of objects) {
// wait for this to resolve and after that move to next object
let result = await doSomething(object);
}
}
The code has to be contained inside an async function and then you can use await to tell the interpreter to wait for the promise to resolve before continuing the loop. Note, while this appears to be "blocking" type behavior, it is not blocking the event loop. Other events in the event loop can still be processed during the await.
Yes you can use angular.forEach to achieve this.
Here is an example (assuming objects is an array):
// Define the initial promise
var sequence = $q.defer();
sequence.resolve();
sequence = sequence.promise;
angular.forEach(objects, function(val,key){
sequence = sequence.then(function() {
return doSomething(val);
});
});
Here is how this can be done using array.reduce, similar to #friend00's answer (assuming objects is an array):
objects.reduce(function(p, val) {
// The initial promise object
if(p.then === undefined) {
p.resolve();
p = p.promise;
}
return p.then(function() {
return doSomething(val);
});
}, $q.defer());
check $q on angular:
function outerFunction() {
var defer = $q.defer();
var promises = [];
function lastTask(){
writeSome('finish').then( function(){
defer.resolve();
});
}
angular.forEach( $scope.testArray, function(value){
promises.push(writeSome(value));
});
$q.all(promises).then(lastTask);
return defer;
}
The easiest way is to create a function and manually iterate over all the objects in the array after each promise is resolved.
var delayedFORLoop = function (array) {
var defer = $q.defer();
var loop = function (count) {
var item = array[count];
// Example of a promise to wait for
myService.DoCalculation(item).then(function (response) {
}).finally(function () {
// Resolve or continue with loop
if (count === array.length) {
defer.resolve();
} else {
loop(++count);
}
});
}
loop(0); // Start loop
return defer.promise;
}
// To use:
delayedFORLoop(array).then(function(response) {
// Do something
});
Example is also available on my GitHub:
https://github.com/pietervw/Deferred-Angular-FOR-Loop-Example
I use a simple solution for a connection to a printer that wait till the promise is over to go to the next.
angular.forEach(object, function(data){
yourFunction(data)
.then(function (){
return;
})
})
It might help someone as I tried several of above solution before coming up with my own that actually worked for me (the other ones didn't)
var sequence;
objects.forEach(function(item) {
if(sequence === undefined){
sequence = doSomethingThatReturnsAPromise(item)
}else{
sequence = sequence.then(function(){
return doSomethingThatReturnsAPromise(item)
});
}
});
It worked for me like this. I don't know if it is a right approach but could help to reduce lines
function myFun(){
var deffer = $q.defer();
angular.forEach(array,function(a,i) {
Service.method(a.id).then(function(res) {
console.log(res);
if(i == array.length-1) {
deffer.resolve(res);
}
});
});
return deffer.promise;
}
myFun().then(function(res){
//res here
});