jQuery.when() not working as expected - javascript

I have a series of asynchronous actions whose responses must be synchronized in order to run a final action. I'm using The Deferred object and the when() and done() methods to achieve this, but for some reason, the final action within done() is always executed when the first resolve() of the responses is invoked.
This is the code I have, cut a bit short to make it clearer:
// Central function that creates a Deferred object and returns a
// Promise from it. when the passed request is done,
// the Deferred is resolved
var getData = function(url, successFunction) {
var deferred = $.Deferred();
$.ajax({
url: url,
method: 'get',
dataType: 'json'
}).done(function(p) {
successFunction(p);
deferred.resolve(p);
}).fail(function(p){
deferred.reject(p);
});
return deferred.promise();
};
// Success actions to be called for each asynchronous request
var populateDestinations = function(data) {
// synchronous actions on DOM
};
var populateTaxes = function(data) {
// synchronous actions on DOM
};
var populatePayment = function(data) {
// synchronous actions on DOM
};
// Actions that return a Promise to control the resolution of
// all the deferred objects
var getCustomerDestinations = function(customerId) {
var url = $modal.data('url_destinations') + customerId;
return getData(url, populateDestinations);
};
var getCustomerTaxes = function(customerId) {
var url = $modal.data('url_taxes') + customerId;
return getData(url, populateTaxes);
};
var getCustomerPayment = function(customerId) {
var url = $modal.data('url_payment') + customerId;
return getData(url, populatePayment);
};
var populateFields = function() {
// final actions
};
$.when(getCustomerDestinations(customer_id),
getCustomerTaxes(customer_id),
getCustomerPayment(customer_id))
.done(function(){
populateFields();
});
populateFields() is being called whenever one of the "promised" functions is resolved, not when all of them are resolved.
Any idea what I'm doing wrong? Maybe I haven't yet grasped the concept of the Deferred object.

you really dont need to use any deferred objects to track ajax calls, instead you can just use the promise object returned from $.ajax within $.when().
JQUERY CODE:
var getData = function(url, successFunction) {
return $.ajax({
url: url,
method: 'get',
dataType: 'json'
}).then(function(p) {
successFunction(p);
},function(p){
//process error using error callback,
//just like success callbacks
});
};
I order to process individual ajax calls you can use .then() instead of .done() & .fail(), because these will not return any promise object unlike .then() to track the same in .when().
Rest of your code will work as it is.
What jQuery forum says:
As of jQuery 1.8, the deferred.then() method returns a new promise that can filter the status and values of a deferred through a function, replacing the now-deprecated deferred.pipe() method. The doneFilter and failFilter functions filter the original deferred's resolved / rejected status and values. The progressFilter function filters any calls to the original deferred's notify or notifyWith methods. These filter functions can return a new value to be passed along to the promise's .done() or .fail() callbacks, or they can return another observable object (Deferred, Promise, etc) which will pass its resolved / rejected status and values to the promise's callbacks.
refence links :
http://api.jquery.com/jQuery.ajax/#jQuery-ajax-settings
http://api.jquery.com/deferred.then/

Related

Nested then() functions returning the promises earlier even before completion of nested then() function

I have a 2 functions which should call one after the other Like below.
MainFunc().then(DrawChart());
MainFunc() function internally have nested functions like I have mentioned below.
I want MainFuc() to return promise or in other way DrawChart() function should call once the createMultiBatchDropdown() is completed.
I checked some links : Nesting asynchronous jquery promises
But I dont want to use any set timeout or delay functions.
I'm new to the concept of this then() and promise() function.Any help will be appreciated.
function MainFunc(){
var r = $.Deferred();
var xhr = BatchTypeFilterList(data,id).then(function(res){
//Logic goes here
var impactXhr = GetImpactData(id).then(function(result){
var DropXhr = createMultiBatchDropdown('ImpactBatchSearch',result)
})
})
return r.promise(xhr);
}
function BatchTypeFilterList(){
var deferred = $.Deferred();
var xhr = $.ajax({
//Ajax Call
success:function(result){
deferred.resolve(result);
}
})
return deferred.promise(xhr);
}
function GetImpactData(){
var deferred = $.Deferred();
var xhr = $.ajax({
//Ajax Call
success:function(result){
deferred.resolve(result);
}
})
return deferred.promise(xhr);
}
function createMultiBatchDropdown(){
var batchDropDownDeferred = $.Deferred();
//No ajax call normal jquery logic to form dropdown
batchDropDownDeferred.resolve(data);
return batchDropDownDeferred.promise(xhr);
}
GetImpactData returns a Promise, but it's not chained with the outer xhr, which means that calling MainFunc will result in a Promise that resolves before createMultiBatchDropdown has been called. Instead, return the Promise created by createMultiBatchDropdown so that it's chained with the whole Promise chain properly. You also need to return impactXhr to complete the chain. Change to:
var xhr = BatchTypeFilterList(data, id).then(function(res) {
//Logic goes here
var impactXhr = GetImpactData(id).then(function(result) {
return createMultiBatchDropdown('ImpactBatchSearch', result);
})
return impactXhr;
})

jQuery .when() is getting ignored in a loop

I have a custom loop that needs to execute a function before going into the next iteration. This is the code:
function customIteration(arr, i)
{
if (i==arr.length) return;
var message = arr[i];
jQuery('#stepTwo #num1StepTwo').html('Expires: ' + $num1);
jQuery('#stepTwo #num2StepTwo').html(jQuery(message).find('.num2').text());
jQuery('#stepTwo #num3StepTwo').html(jQuery(message).find('.num3').text());
i++;
jQuery.when(mySpecialFunction()).then(customIteration(arr, i));
}
mySpecialFunction():
function mySpecialFunction(){
return jQuery.ajax({
url: "https://api.site.com/customurl",
dataType: "jsonp",
data: { data1: $data1, data2: $data2 },
success: function (data) {
...some code...
},
error: function (e) {
...some other code...
}
});
}
problem is, I see in Fiddler that the url is being hit immediately by all the instances of the loop above, without waiting to get a response from the ajax code inside mySpecialFunction(). This is of course messes up the results I should get.
Try using .done()
in fact,
.done() has only success callback.
.then() has both success and fail callbacks.
As of jQuery 1.8, the deferred.then() method returns a new promise that can filter the status and values of a deferred through a function, replacing the now-deprecated deferred.pipe() method.
The deferred.done() method accepts one or more arguments, all of which can be either a single function or an array of functions.
Since deferred.done() returns the deferred object, other methods of the deferred object can be chained to this one, including additional .done() methods. When the Deferred is resolved, doneCallbacks are executed using the arguments provided to the resolve or resolveWith method call in the order they were added.
Try it using .done() and a recursive function, should be easier to implement and understand.
Like this:
(function recursive(arr,i){
jQuery.ajax({
url: "https://api.site.com/customurl",
dataType: "jsonp",
data: { data1: $data1, data2: $data2 },
success: function (data) {
...some code...
},
error: function (e) {
...some other code...
}
}).done(function(data){
var message = arr[i];
jQuery('#stepTwo #num1StepTwo').html('Expires: ' + $num1);
jQuery('#stepTwo#num2StepTwo').html(jQuery(message).find('.num2').text());
jQuery('#stepTwo #num3StepTwo').html(jQuery(message).find('.num3').text());
if(i!=arr.length){
recursive(++i);
}
}); /// End done
})(0); ///End recursive function
What this does is make sure your single iteration ends before calling itself up again and continuing iterating.
So basically your function is calling itself when it's done with a single iteration, and continues until everything has been iterated, then stops.

WinJS : Returning promise from a function

I would like to make a function that returns a promise. That promise would contain the data of an asynchronous call made in the function. What I want it to look like :
//Function that do asynchronous work
function f1() {
var url = ...
WinJS.xhr({ url: url }).then(
function completed(request) {
var data = ...processing the request...
...
},
function error(request) {
...
});
}
//Code that would use the result of the asynchronous function
f1().done(function(data) {
...
});
The only way I found to make this work is to pass a callback to f1 and call it when I have the data. Using callbacks though seems to defeat the goal achieved by promises. Is there a way to make it work like above? Also, I could return WinJS.xhr in f1, but the done method of f1 would return the request and not the "data".
There's little to change:
function f1() {
var url = …;
return WinJS.xhr({ url: url }).then(function completed(request) {
// ^^^^^^
var data = …; // processing the request
return data;
// ^^^^^^^^^^^
});
}
//Code that would use the result of the asynchronous function
f1().done(function(data) {
…
}, function error(request) {
… // better handle errors in the end
});
You don't want to return the WinJS.xhr() itself indeed, but you want to return the result of the .then(…) call which is exactly the promise that resolves with the return value of the callback. This is one of the main features of promises :-)

Promises call their success callback too late

I have a code as follows:
products.SaveApplications = function (product) {
var promises = [];
ko.utils.arrayForEach(product.Applications(), function (item) {
var applicationToSend = ko.mapping.toJS(item);
promises.push($.ajax({
type: "POST",
contentType: "application/json",
url: serviceUrl + "/InsertApplication",
data: JSON.stringify(applicationToSend),
success: function (data) {
alert("Doing success callback now.");
item.ID(data);
}}));
});
return promises;
}
Then a function products.SaveEnvironments, which is basically the same, except it calls a different function in my controller, and a function products.SaveInstallations, which is, again, mostly the same, except it needs to wait for Environments and Applications to save, because it has some dependant data (Application.ID and Environment.ID).
Now I have a function, which calls both these functions and waits for them until both have finished:
products.SaveChanges = function (product) {
// (...)
var promises = products.SaveApplications(product);
promises = promises.concat(products.SaveEnvironments(product));
$.when(promises).done(function () {
alert("we shouldn't see this before the other message!");
products.SaveInstallations(product);
});
};
The problem is that the alert box with "We shouldn't see this message before the other message!" actually appears before the "Doing success callback now", which means my ID properties are null at the time. It leads me to the conclusion that the $.when call only does the actual promises, but doesn't wait for the success callbacks to finish. How can I make it wait? Thank you.
You are using $.when incorrectly. You can't pass multiple promises to it as an array; if you do that, it treats the array just like any other non-promise value and turns it into a completed promise with the array of the still-pending promises as its result. That, of course, is not useful.
You need to use .apply instead to simulate calling $.when with each promise as a separate argument, like this:
$.when.apply(null, promises).done(function () { ... });

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