Jquery .when and multiple .load - javascript

I want to have one callback function after actions are done, I'm trying something like this:
$.when(
$('#detail1').load('/getInfo.php'),
$('#detail2').load('/getOther.php')
).then(function(a,b){
alert("done");
});
The problem is that the callback function is firing before the actions are finished.

This is because jQuery.when() expects jQuery.Deferred instances while load() returns an jQuery instance (see http://api.jquery.com/jQuery.when/ and http://api.jquery.com/load/).
You can work around this issue:
// Create two Deferred instances that can be handed to $.when()
var d1 = new $.Deferred();
var d2 = new $.Deferred();
// Set up the chain of events...
$.when(d1, d2).then(function() {
alert('done');
});
// And finally: Make the actual ajax calls:
$('#detail1').load('/getInfo.php', function() { d1.resolve(); });
$('#detail2').load('/getOther.php', function() { d2.resolve(); });

I do a similar code but for images in a more dynamic way. Hope that it help.
var deferreds = [];
// Create a deferred for all images
$('.my-img-class').each(function() {
deferreds.push(new $.Deferred());
});
var i = 0;
// When image is loaded, resolve the next deferred
$('.my-img-class').load(function() {
deferreds[i].resolve();
i++;
});
// When all deferreds are done (all images loaded) do some stuff
$.when.apply(null, deferreds).done(function() {
// Do some stuff
});

Related

How to resolve race condition when using $.when

I am trying to use jQuerys $.when() to load a bunch of localization resources before initializing the control on the client side:
var fooControl = (function($, kendo, _) {
var
initResources = function() {
return $.when(
window.clientResources.getAll("Messages").done(function(d) {
resources["Messages"] = d;
}),
window.clientResources.getAll("Cost").done(function(d) {
resources["Cost"] = d;
})
);
},
init = function(options) {
/*... */
}
}
//calling:
$(function() {
fooControl.initResources().then(fooControl.init(options));
});
What I want to achieve is, that initResources waits until the resources are loaded up and assigned to their variables. They are either loaded up from an API endpoint or localStorage, if the data is cached.
What actually happens is, that I am receiving an error Cannot read property 'CostType' of undefined, which indicates, that the Cost resources haven't been fully loaded yet.
So I suspect, that the calls to window.clientResources.getAll() are being resolved properly, but not the following .done method and this then results in a race condition, the resources are losing.
How can I make sure, that the whole call stack, including the assignment of the resources variable has been resolved and only then the following init function is called?
You are invoking the init immediately and passing its return value as success callback handler, A simple solution would be to use a anonymous method
fooControl.initResources().then(function(){
fooControl.init(options);
});
You could use $.Deferred() for this, then resolve that only when the resources have been loaded.
Also as Satpal noted, then needs to be passed either an anonymous function or a function reference.
var fooControl = (function($, kendo, _) {
var initResources = function() {
var deferred = $.Deferred();
$.when(
window.clientResources.getAll("Messages"),
window.clientResources.getAll("Cost")
).done(function(msgData, costData) {
resources["Messages"] = msgData;
resources["Cost"] = costData;
deferred.resolve();
});
return deferred.promise();
},
init = function(options) {
/*... */
}
}
//calling:
$(function() {
fooControl.initResources().then(fooControl.init.bind(this, options));
});

Unable to figure out jQuery deferred done

I am new to jquery deferred and I spent a while looking at similar questions on here but to no avail. I could use some help
I have a function that grabs the form input values and a function that saves those values. The form inputs values are not being grabbed fast enough before the save items function executes. I am trying to solve this by using jquery deferred but cannot get it to work. I also tried to use .when and .done.
When I hard coded the parameters in my js file, Attempt 2 worked but not when I passed parameters on an onclick or in my console.
Any help would be appreciated!
Attempt 1:
function deferredTest1(w,x,y,z) {
var d1 = $.Deferred();
d1.done(function() {
SaveNewItem(x,y,z);
});
function getFormValues(w) {
$(w).each(function(i,obj) {
var input = $(this).val();
var inputID = $(this).attr('id');
str = str.concat(",'"+inputID+"':'"+input+"'");
});
}
d1.resolve();
}
Attempt 2:
function deferredTest2(w,x,y,z) {
var formvalues = getFormValues(w);
$.when(formvalues).done(SaveNewItem(x,y,z));
}
Your second try was closest. The $.when call takes a deferred, so we create one first, then use that in the $.when call. It is resolved in the getFormValues method after the loop is completed. At this time the then callback is executed.
var deferred = $.Deferred();
function deferredTest3(w,x,y,z) {
$.when(deferred).then(function () {
SaveNewItem(x,y,z);
});
}
function getFormValues(w) {
$(w).each(function(i,obj) {
var input = $(this).val();
var inputID = $(this).attr('id');
str = str.concat(",'"+inputID+"':'"+input+"'");
});
deferred.resolve();
}
There is no asynchronous action here though, so a deferred is not necessary. A deferred is used to manage various asynchronous requests and their callbacks if they depend on one another.

How can I make use of jQuery promises in custom async functions?

So, I've built an api object that can be included in any JavaScript file via require.js. In the api object, I have calls to create Backbone models/collections like the one shown below:
getDatapoints: function(attributes, callback) {
require(['models/datapoint'], function(Datapoint){
var datapoint = new Datapoint.DatapointCollection(attributes);
datapoint.fetch({success: function(data){
return callback(data.toJSON());
}});
});
}
I want to have a way of being able to start multiple calls and run a callback function once ALL calls have completed. It looks like jQuery's $.when function does what I want, but I'm not sure how to get it to work with anything besides $.ajax calls.
Am I looking in the right place? Should I be looking at something like q.js?
Expanding on #mattacular's answer:
API = {
getDatapoints: function (attributes){
var dfd = $.Deferred();
require(['models/datapoint'], function(Datapoint){
var dataPoints = new Datapoint.DatapointCollection(attributes);
dataPoints.fetch().then(function (points){
dfd.resolve(points.toJSON());
}, function (error){
dfd.reject(error);
});
});
return dfd.promise();
},
getAllDatapoints: function (arrayOfAttributes){
var arrayOfPromises = arrayOfAttributes.map(this.getDatapoints);
return $.when.apply($, arrayOfPromises);
}
}
And where you're actually calling the getAllDatapoints method:
var allDatapointAttributes = [{...}, {...}, {...}];
API.getAllDatapoints(allDatapointAttributes).done(function(){
console.log.apply(console, arguments);
// should output an array of arrays containing dataPoint
// objects when all the requests have completed successfully.
});
You can do this using jQuery's Deferred object. Here is a quick example:
getDatapoints: function (attributes, callback() {
var dataPoints = $.Deferred();
// perform async calls here
// when "done," call dataPoints.resolve() or dataPoints.reject() accordingly
return dataPoints.promise();
}
edit: removed outdated tutorial

Managing Synchronous and Asynchronous JavaScript Functions with jQuery's Deferred

I need to run a series of calls over websockets via Socket.IO (client-side). Since I'm not using $.ajax, jQuery's deferred functions won't integrate as well and I'll have to manually handle promises. With every websocket call, I pass a callback and I'm quickly seeing how this project could spiral out of control. Here's a simplified example of how my websocket calls work (excluding all connection handling code):
function js2node(nodeFunction, data, callback){
socket.emit('incoming', nodeFunction, data, callback);
}
function sampleServerCall(){
js2node('sampleCall', 'something', 'sampleCallback');
}
function sampleCallback(json){
// Handle data
}
sampleServerCall();
I will be talking to the server quite a bit, all calls will be asynchronous, but some will need to come back in a specific order. Enter jQuery deferred. Here is some working code:
var deferredArray = [];
$(function(){
$.when( // Any order
getData1(),
getData2()
).then(function(){ // Must have responses from dataCallback1 and dataCallback2 before doing this...
$.when( // Any order
getData3(),
getData4()
).then(function(){ // Must have responses from dataCallback3 and dataCallback4 before doing this...
getData5();
});
});
});
function getData1(){
js2node('data1', 'something', 'dataCallback1');
deferredArray[0] = new $.Deferred();
return deferredArray[0].promise();
}
function getData2(){
js2node('data2', 'something', 'dataCallback2');
deferredArray[1] = new $.Deferred();
return deferredArray[1].promise();
}
function getData3(){
js2node('data3', 'something', 'dataCallback3');
deferredArray[2] = new $.Deferred();
return deferredArray[2].promise();
}
function getData4(){
js2node('data4', 'something', 'dataCallback4');
deferredArray[3] = new $.Deferred();
return deferredArray[3].promise();
}
function getData5(){
js2node('data5', 'something', 'dataCallback5');
deferredArray[4] = new $.Deferred();
return deferredArray[4].promise();
}
function dataCallback1(json){
// Handle data
deferredArray[0].resolve();
}
function dataCallback2(json){
// Handle data
deferredArray[1].resolve();
}
function dataCallback3(json){
// Handle data
deferredArray[2].resolve();
}
function dataCallback4(json){
// Handle data
deferredArray[3].resolve();
}
function dataCallback5(json){
// Handle data
deferredArray[4].resolve();
}
As you can see, I'm still stuck with nested callbacks from the way I'm using when/then and nesting could potentially go deeper as I add functionality. Deferred is a new concept to me but I've read it's supposed to help in situations such as this. I feel like there has to be a better way than what I'm currently doing. Can anyone help me set this up more efficiently?
You can do more with .then:
$(function(){
$.when(
doSock('data1', 'something'),
doSock('data2', 'something')
).then(function(data1, data2){
return $.when(
doSock('data3', 'something'),
doSock('data4', 'something')
);
}).then(function(data3, data4){
return doSock('data5', 'something');
});
});
That way your nesting never goes deeper than that.
(i used adeneo's helper method)
Using a better helper function sure would help, but you'd still have to structure the calls with $.when and $.then to execute them in the proper order
function doSock(nodeFunction, data) {
var def = new $.Deferred();
socket.emit('incoming', nodeFunction, data, function(received) {
def.resolve(received)
});
return def.promise();
}
$(function(){
$.when(
doSock('data1', 'something'),
doSock('data2', 'something')
).then(function(data1, data2){
$.when(
doSock('data3', 'something'),
doSock('data4', 'something')
).then(function(data3, data4){
doSock('data5', 'something');
});
});
});

JQuery Deferred. Using $.when and .progress()

I am writing a parser littered with Async Tasks. I use JQuery promises to control and order the async tasks. This is a psuedo code version of the constructor function:
/**
* #constructor
*/
function Parser(json)
{
return $.when(
this.delete().then(this.insert(json)),
this.doSomething(),
this.doSomethingElse().then(this.update(json))
)
};
and this is what an example function looks like:
Parser.prototype.doSomething = function()
{
var dfd = $.Deferred();
exampleTask(dfd.reject, dfd.resolve);
return dfd.promise();
};
From the JQuery docs:
In the case where multiple Deferred objects are passed to jQuery.when,
the method returns the Promise from a new "master" Deferred object
that tracks the aggregate state of all the Deferreds it has been
passed
How can I use .progress() to notify anybody that cares about the overall progress of the Parser?
e.g.
var parser = new Parser(json);
parser.progress(function(prog){console.log(prog});
Heres a fiddle with what I'm trying to do:
http://jsfiddle.net/ashanova/RjULA/2/
Use deferred.notify() to call the progressCallbacks. For example:
function doSomething() {
var dfd = $.Deferred();
var count = 0;
var intervalId = setInterval(function() {
dfd.notify(count++);
count > 3 && clearInterval(intervalId);
}, 500);
return dfd.promise();
};
var promise = doSomething();
promise.progress(function(prog) {
console.log(prog);
});​
DEMO.

Categories

Resources