Chain queuing callbacks results - javascript

I got a loop like this:
for ( var current in all )
{
//load the item
prepare.load( all[current].resource , function( result ) {
doSomethingWithResult(result);
});
}
function AllItemsLoaded()
{
}
My goal is to execute AllItemsLoaded() after all items are loaded and the code in the callbacks is executed, e.g. For every item callback should be called and DoSomethingWithResult() should be executed before AllItemsLoaded() is called, all these items are loaded asynchronously.
I've tried Jquery Deferred/pipe and my code looked like this:
var chain = new $.Deferred().resolve();
for ( var current in all )
{
chain = chain.pipe(function(res){
prepare.load( all[current].resource , function( result ) {
doSomethingWithResult(result);
});
});
//if I do a return here, the pipe will continue without getting the result,
so I need to continue the pipe after load's callback and
doSomethingWithResult is executed
}
chain.done(AllItemsLoaded);

Deferred is a good idea. However, you need to wait on the promise. Here's a method using when to wait on all the promises without doing them in order:
var loads = [];
for ( var current in all )
{
(function(){
var deferred = new $.Deferred();
prepare.load( all[current].resource , function( result ) {
doSomethingWithResult(result);
deferred.resolve(result);
});
loads.push(deferred.promise());
})();
}
$.when.apply(null, loads).then(AllItemsLoaded);
First create a new deferred for each load. Place its promise in a collection. After the load, resolve the deferred. Wait on all of the loads with $.when().

Is this what you need?
From: http://aabs.wordpress.com/2009/12/16/sequential-script-loading-on-demand/
function LoadScriptsSequentially(scriptUrls, callback)
{
if (typeof scriptUrls == 'undefined') throw "Argument Error: URL array is unusable";
if (scriptUrls.length == 0 && typeof callback == 'function') callback();
$.getScript(scriptUrls.shift(), function() { LoadScriptsSequentially(scriptUrls, callback); });
}

I would approach it as such (below), where you replace each $.get() with your own asynchronous object, with it's own individual complete handler.
$(document).ready(function() {
$.when(
$.get("ajax.php?a=b"),
$.get("ajax.php?a=c"),
$.get("ajax.php?a=d")
).then(
function() {
// both AJAX calls have succeeded
alert("All Done");
},
function() {
// one of the AJAX calls has failed
alert("One or more failed");
}
);
});

First thing is to use .get() or .post() not .load(), the reason being that .load() returns jQuery while the other two return jqXHR (ie a promise), which is what you want here.
Next thing is to provide an array in which to accumulate the jqXHR promises.
Last you need to know how to get $.when() to act on the array of promises, to do something when all of them are resolved (or an error occurs).
The whole thing looks like this :
var promises = [];//new Array
for ( var current in all ) {
prepare.get( all[current].resource, function( result ) {
doSomethingWithResult(result);
});
}
$.when.apply(null, promises).then(AllItemsLoaded, myErrorHandler);

Related

Is $.when() or $.when.apply() the correct call for getting a promise out of a forEach?

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?

chaining jquery .when().then() in a loop with fixed end of chain call

The closest answer I could find was this https://stackoverflow.com/a/17216555/2834734
The most common use for .then is chaining ajax requests:
$.ajax({...}).then(function(){
return $.ajax({...}); }).then(function(){
return $.ajax({...}); }).then(function(){
return $.ajax({...}); }).then(function(){
return $.ajax({...}); });
this can easily be done in a loop
However it's the looping procedure I'm having difficulty with plus I have some unusual circumstances.
A brief explanation is, I have an array of requests that I need to loop through, some will invoke an ajax load and others will not. I need them to run consecutively but also run a specific function call at then end.
Here is a simple(I hope) sample of my situation:
// Here is my flow of logic
var thingsToDo = new tasks(); // Initiate the constructor, explained below
// Loop through the requests array and process them consecutively
for (var i in thingsToDo.requests) {
$.when(thingsToDo.jqxhr).then(function() {
thingsToDo.requests[i].fn();
})
}
// Run my final function at the end of the chain.
$.when(thingsToDo.jqxhr).then(function() {
runOnceAllComplete();
});
This is the constructor class the above is based on.
// Constructor
function tasks() {
_tasks_ = this; // automatic global var
this.jqxhr = undefined; // Var to monitor ajax calls
this.requests = [ // list of tasks to run.
{
url: 'file1.php',
fn: function() {
_tasks_.load(this.url);
console.log('file1 loaded');
}
}, {
url: 'file2.php',
fn: function() {
_tasks_.load(this.url);
console.log('file2 loaded');
}
}, {
noUrl: true, // Note there is no file to load here
fn: function() {
console.log('no file here to load, but process something else');
$('body').css("background-color", "blue");
}
}, {
url: 'file3.php',
fn: function() {
_tasks_.load(this.url);
console.log('file1 loaded');
}
},
];
this.load = function(file) { // This runs the ajax call and resets this.jqxhr
this.jqxhr = $.get(file);
}
}
function runOnceAllComplete() {
alert('hooray!, we finished');
}
The tricky part I have is the requests are created dynamically so there can be 1-n many requests to perform, which is why I chose to loop, and they must be performed in that order.
As mentioned some requests will invoke an ajax call and others may not, this doesn't seem to break $.when().then(), but the problem is the loop continues before the promise is resolved and my final function happens before the final request.
Still trying to get my head around promises, the first time I've used them.
Try including return statement at fn , this.load ; adding .promise() chained to $("body") at fn to return a jQuery promise object ; using Function.prototype.apply() , $.map() at $.when()
fn: function() {
// added `return`
return _tasks_.load(this.url);
}
this.load = function(file) {
this.jqxhr = $.get(file);
// added `return`
return this.jqxhr
}
fn: function() {
console.log('no file here to load, but process something else');
// added `return` , `.promise()`
return $('body').css("background-color", "blue").promise();
}
$.when.apply($, $.map(thingsToDo.requests, function(task) {
return task.fn()
})).then(runOnceAllComplete)
See also Pass in an array of Deferreds to $.when() , What does $.when.apply($, someArray) do?
however I'm encountering a problem, using the .map() it doesn't wait
for each request to complete before processing the next one. I need
each one to complete before moving to the next.
Try using .queue() , which will calls functions in queue sequentially, and only when next is called at current function
$(thingsToDo).queue("tasks", $.map(thingsToDo.requests, function(task) {
return function(next) {
// call next function in `"tasks"` queue
// when current function completes using `.then(next)`
return task.fn().then(next)
}
})).dequeue("tasks").promise("tasks").then(runOnceAllComplete)
See .queue() , .promise() , Execute function queue in javascript

Chaining jQuery promises in parallel using simple alternative to $.when.apply

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/

Execute a function after the recursive asynchronous ajax calls

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/

Increment for only after the previous interaction has been finished (callback)

I'm having a problem with callback functions in javascript. What I want to do is: loop on a for and call a function passing i as parameter. With that in mind, I have to loop to the next interaction only after the previous one has been finished. I don't know if this is a problem but inside the function I'm sending i as parameter, I have another callback function. Here is my code:
for(i=0; i<10; i++) {
aux(i, function(success) {
/*
* this should be made interaction by interaction
* but what happens is: while I'm still running my first interaction
* (i=0), the code loops for i=1, i=2, etc. before the response of
* the previous interaction
*/
if(!success)
doSomething();
else
doSomethingElse();
});
}
function aux(i, success) {
... //here I make my logic with "i" sent as parameter
getReturnFromAjax(function(response) {
if(response)
return success(true);
else
return success(false);
});
});
function getReturnFromAjax(callback) {
...
$.ajax({
url: myUrl,
type: "POST",
success: function (response) {
return callback(response);
}
});
}
jQuery's Deferred can be a bit tricky to get right. What you'll have to do is stack your promises in a chain. For example:
var
// create a deferred object
dfd = $.Deferred(),
// get the promise
promise = dfd.promise(),
// the loop variable
i
;
for(i = 0; i < 10; i += 1) {
// use `then` and use the new promise for next itteration
promise = promise.then(
// prepare the function to be called, but don't execute it!
// (see docs for .bind)
aux.bind(null, i, function(success) {
success ? doSomethingElse() : doSomething();
})
);
}
// resolve the deferred object
dfd.resolve();
for this to work, aux must also return a promise, but $.ajax already does this, so just pass it through and everything should work:
in aux:
function aux(i, callback) {
console.log('executing for `aux` with', i);
// return the ajax-promise
return getReturnFromAjax(function(response) {
callback(Boolean(response));
});
}
in getReturnFromAjax:
function getReturnFromAjax(callback) {
// return the ajax-promise
return $.ajax({
url: '%your-url%',
type: '%method%',
success: function (response) {
callback(response);
}
});
}
demo: http://jsbin.com/pilebofi/2/
I'd suggest that you'd look into jQuery's Deferred Objects and jQuery.Deferred()-method instead of making your own callback queue functions (as you are already using jQuery anyway).
Description: A constructor function that returns a chainable utility
object with methods to register multiple callbacks into callback
queues, invoke callback queues, and relay the success or failure state
of any synchronous or asynchronous function.
I don't have experience with jQuery, but your callback looks a bit fishy to me.
In plain JS I'd suggest trying something among the lines of this:
function yourMainFunction
{
function callbackHandler(result)
{
// Code that depends on on the result of the callback
}
getAjaxResults(callbackHandler);
}
function getAjaxResults(callbackHandler)
{
// Create xmlHttpRequest Handler, etc.
// Make your AJAX request
xmlHttp.onreadystatechange = function()
{
if (xmlHttp.readyState == 4 && xmlHttp.status==200)
{
// Do stuff you want to do if the request was successful
// Define a variable with the value(s) you want to return to the main function
callbackHandler(yourReturnVariable);
}
}
}

Categories

Resources