Having a really hard time with this issue, and I know $.when() can be used like so (with multiple AJAX statements) to promise you when they have all finished.
http://jsfiddle.net/M93MQ/
$.when(
$.ajax({ url: '/echo/html/', success: function(data) {
alert('request 1 complete')
}
}),
$.ajax({ url: '/echo/html/', success: function(data) {
alert('request 2 complete')
}
})
).then( function () { alert('all complete'); });
But this only works with raw $.ajax(), is there anyway to have this same functionality with function calls, that in turn have the ajax inside them (and other random logic) ?
Pseudo-code idea:
// The functions having the AJAX inside them of course
$.when(ajaxFunctionOne, ajaxFunctionTwo).then(function () {
alert('all complete');
});
Sure, have the function return a promise object.
function ajaxFunctionOne() {
return $.ajax(...)
}
function ajaxFunctionTwo() {
var dfd = $.Deferred();
// on some async condition such as dom ready:
$(dfd.resolve);
return dfd.promise();
}
function ajaxFunctionThree() {
// two ajax, one that depends on another
return $.ajax(...).then(function(){
return $.ajax(...);
});
}
$.when(ajaxFunctionOne(),ajaxFunctionTwo(),ajaxFunctionThree()).done(function(){
alert("all complete")
});
Related
I have two nested functions like this:
var userhascompany;
$(document).ready(function(){
checkforcompany();
if (Object.keys(localStorage).length > 0) {
getaddjobcache();
}
});
The checkforcompany(); function looks like this:
$.ajax({
url: 'addjob_companycheck.php',
success: function(dataresponse) {
userhascompany = dataresponse;
}
});
The function getaddjobcache(); should only get called, when the variable userhascompany has a value or rather the ajax request has finished.
Modify checkforcompany() to return the jqXHR created by $.ajax(), since it implements the thenable interface. I recommend using jQuery 3.x for consistent promise behavior, but if you must use an older version, you can usually get away with wrapping it in a call to Promise.resolve() in order to coerce the behavior to that of a compliant promise.
function checkforcompany () {
return $.ajax({
url: 'addjob_companycheck.php'
});
}
$(() => {
checkforcompany().then(dataresponse => {
const userhascompany = dataresponse;
if (Object.keys(localStorage).length > 0) {
getaddjobcache(userhascompany);
}
});
});
Also don't rely on global variables in order to pass values between contexts. Make getaddjobcache() accept userhascompany as a parameter instead, as shown above.
Lastly, $() is recommended by jQuery instead of $(document).ready().
There are two approaches for this one is using callback functions other is using .then block or async-await
Using Callback function
var userhascompany;
$(document).ready(function(){
checkforcompany();
});
function checkforcompany(){
$.ajax({
url: 'addjob_companycheck.php',
success: function(dataresponse) {
userhascompany = dataresponse;
if (Object.keys(localStorage).length > 0) {
getaddjobcache();
}
}
});
}
Using promise .then
var userhascompany;
$(document).ready(function(){
checkforcompany()
.then(function(dataresponse){
userhascompany = dataresponse;
if (Object.keys(localStorage).length > 0) {
getaddjobcache();
}
})
});
function checkforcompany(){
return new Promise(function(resolve, reject){
$.ajax({
url: 'addjob_companycheck.php',
success: function(dataresponse) {
resolve(dataresponse)
}
error: function(err) {
reject(err)
}
});
})
}
Using jQuery ajax .done
var userhascompany;
$(document).ready(function(){
checkforcompany()
.done(function(dataresponse){
userhascompany = dataresponse;
if (Object.keys(localStorage).length > 0) {
getaddjobcache();
}
})
});
function checkforcompany(){
return $.ajax({
url: 'addjob_companycheck.php',
});
})
}
I've got to run a recursive process and the promises are not working as I want. This is the code:
var openAllLeves = function () {
openAll = 1;
$.when(openLevels()).then(openAll = 0);
}
var openLevels = function () {
var promises = [];
$('.myClass:not([data-type="T"])').each(function () {
var defer = $.Deferred();
$.when(loadLine($(this)).then(promises.push(defer)));
});
return $.when.apply(undefined, promises).promise();
}
var loadLine = function (thisObj) {
var defer = $.Deferred();
switch(nivel) {
case 1:
$.when(getPT($(thisObj).attr('data-a'))).then(defer.resolve());
break;
case 2:
// ...
}
return defer.promise();
}
var getPT = function (psn) {
var defer = $.Deferred();
var payload = { /* parameters... */ };
$.ajax({
url: webmethod,
data: payload,
type: "POST",
contentType: "application/json; charset=utf-8",
dataType: "json",
timeout: 10000,
success: function (data) {
$.when(paintPT(data)).then(function () {
if (openAll)
openLevels(), defer.resolve();
});
}
});
return defer.promise();
}
My problem is that openAll's value changes to 0 before being evaluated in the ajax function success code so only one iteration is performed and the recursivity is not done. It looks like .then is performed before resolving the array of promises.
The code is a little bit confusing so any help is appreciated.
Thanks in advance.
Avoid the deferred antipattern!
Also, when you pass something to .then(), it must be callback function, calling promises.push(defer), defer.resolve() and openAll = 0 or so does not work, it would execute that expression right away instead of waiting for the promise.
The $.when() and .promise() calls are mostly superfluous. Drop them.
function openAllLeves () {
openAll = 1;
openLevels().then(function() {
openAll = 0
});
}
function openLevels() {
var promises = [];
$('.myClass:not([data-type="T"])').each(function () { // using `map` would be even better
promises.push(loadLine($(this)));
});
return $.when.apply($, promises);
}
function loadLine(thisObj) {;
switch(nivel) {
case 1:
return getPT($(thisObj).attr('data-a'))
case 2:
// ...
}
}
function getPT(psn) {
var payload = { /* parameters... */ };
return $.ajax({
url: webmethod,
data: payload,
type: "POST",
contentType: "application/json; charset=utf-8",
dataType: "json",
timeout: 10000,
}).then(function (data) {
return paintPT(data);
}).then(function () {
if (openAll)
openLevels();
});
}
Btw, you will probably want to chain the if (openAll) openLevels(); to the return value of openLevels(), not to each single request promise.
One big problem in your code is that you're calling the functions on the then callback and not passing them to it. For instance:
.then(defer.resolve());
This way you are passing the value of defer.resolve() to the then callback and not the function that should be called when the async action finished. You should be doing something like this:
.then(defer.resolve.bind(defer));
The same applies for the rest of the code.
You should take a look at the promise spec
Particularly
If onFulfilled is not a function, it must be ignored.
EDIT
As pointed out by Bergi you should avoid the deferred antipattern.
Thank you both for your responses. I'm working on this changes. This way, I understand that .then() only waits for the promise when a function is passed. So the right way to resolve a promise in .then() would be..
.then(function() {
defer.resolve();
})
¿?
I need to wait until all my ajax functions are done, and then continue the exectution.
My particular case is that I need to translate some fields in a form before submitting it. I translate them with an ajax call to an external site. Depending on some values in the form i would need to do more or less translations. When all the translations are done (if any) I have to validate the form with ajax, and if its valid, then submit.
This is my aproach:
First, I have a function that sends the ajax call and do stuff with the data received:
function translate(...) {
$("#ajaxCounter").val(parseInt($("#ajaxCounter").val()) + 1);
$.ajax({
...
success:function(data) {
...
$("#ajacCounter").val(parseInt($("#ajaxCounter").val()) - 1);
}
});
Then, when the form is to be submitted I execute the following code:
$("#form").submit(function() {
translatable_fields.each(function() {
translate(...);
});
while (parseInt($("#ajaxCounter").val()) > 0) { null; }
if (!(this).hasClass('ready')) {
$.ajax({
//validation
success: function(data) {
if (data['isValid']) {
$("#form").addClass('ready');
$("#form").submit();
}
}
});
}
return true;
});
The problem is that the while loop in the submit function never ends.
If I execute the code without the while loop I can see the ajaxCounter input increasing when the translation functions start and decreasing when they end.
You can achieve this in a much neater fashion using the deferred objects returned from a $.ajax call. First you should get the translate() function to return the deferred:
function translate(...){
return $.ajax({
// settings...
});
});
Then you can put all those promises in to a single array:
var requests = [];
translatable_fields.each(function(){
requests.push(translate(...));
});
Then you can apply that array to $.when:
$.when.apply($, requests).done(function(schemas) {
console.log("All requests complete");
// do something...
});
You can do this using deferred objects, but you do not need to use $.when.apply with an array if you are only interested in the final completion.
Instead you can chain parallel promises using the pattern promise = $.when(promise, another promise)
Change your translate to return the Ajax promise:
function translate(...) {
...
return $.ajax({
...
});
}
and your promise loop simply becomes:
var promise; // Start with an undefined promise - which is the same as a resolved promise for $.when
translatable_fields.each(function() {
promise = $.when(promise, translate(...));
});
// Wait for all promises to complete
promise.done(function(){
// now do the final code after all the ajax calls complete
});
Notes:
This does create an extra promise per call to $.when, but the overhead is very small and the resulting code is quite simple.
No, you can't just loop like this: the callbacks would never get a chance to be called.
I would do something like this:
function translateAllFields(done) {
var requestsInProgress = 0, doneCalled = false;
translatable_fields.each(function () {
++requestsInProgress;
$.ajax({
//...
success: function (data) {
//...
$("#ajacCounter").val(parseInt($("#ajaxCounter").val()) - 1);
}
}).always(function () {
if (--requestsInProgress === 0) {
done();
doneCalled = true;
}
});
});
if (requestsInProgress === 0 && !doneCalled) {
// in case translatable_fields was empty
done();
}
}
and then:
$("#form").submit(function (e) {
if (!(this).hasClass('ready')) {
e.preventDefault();
e.stopPropagation();
translateAllFields(function() {
$.ajax({
//validation
success: function (data) {
if (data['isValid']) {
$("#form").addClass('ready');
$("#form").submit();
}
}
});
});
}
});
You can use callback
function translate(..., callback) {
$.ajax({
...
success:function(data) {
...
callback(data);
}
});
};
And pass your after ajax code to it
$("#form").submit(function() {
translatable_fields.each(function() {
translate(..., function(result){
if (!(this).hasClass('ready')) {
$.ajax({
//validation
success: function(data) {
if (data['isValid']) {
$("#form").addClass('ready');
$("#form").submit();
}
}
});
}
return true;
});
});
});
I have the following data
var data = [{user:"somename"}, {user:"anothername"}];
I have this function that processes that that, let say checking user if it exist
function process(user) {
$.ajax({
url: 'http://domain.com/api',
success: function(result) {
if(result == 'success')
anotherFunction();
else
console.log('error');
}
})
}
function anotherFunction() {
$.ajax({
// do stuffs
})
}
$.each(data, function(k,v){
process(v.user);
})
The $.each will try to loop even the process and the anotherFunction hasn't finished yet
Question, what can I do to assure that all functions are finished executing before moving on to another index?
I heard I can use jquery deferred.
Collect the promises returned by your AJAX function, if necessary post-processing that result with .then so that it calls anotherFunction() which must also return the result of $.ajax.
function process() {
return $.ajax(...).then(function(result) {
if (result === 'success') {
return anotherFunction();
} else {
return null; // this might need to change...
}
});
}
function anotherFunction() {
return $.ajax(...);
}
var promises = [];
$.each(data, function(k, v) {
promises.push(process(v.data));
}
and then wait for all the promises to be resolved:
$.when.apply($, promises).done(function() {
// all done here
});
NB: in general it's good practise for an AJAX call to produce a non 2xx return code for errors, rather than an error code within a "successful" HTTP call. This then allows the client side code to use normal AJAX error processing (i.e. .fail callbacks) instead of checking for "errors" within AJAX success processing.
I currently have the following code:
function render(url1, url2, message) {
utility.messageBoxOpen(message);
$.getJSON(url1, function (items) {
// Do something
utility.messageBoxClose();
});
$.getJSON(url2, function (items) {
// Do something
});
}
When the function is executed a modal window appears to inform the user something is loading. Initially I only had one $getJSON request so when the request was done the modal window closed as per the code above.
I am looking to add another $getJSON request but want to close the modal window only when both $getJSON requests have completed.
What is the best way of achieving this?
You're looking for $.when()
All jQuery ajax requests (including shortcuts like getJSON) return deferred objects which can be used to control other actions.
var dfd1 = $.getJSON(url1, function (items) {
// Do something
});
var dfd1 = $.getJSON(url2, function (items) {
// Do something
});
$.when(dfd1, dfd2).then(function(){
//both succeeded
utility.messageBoxClose();
},function(){
//one or more of them failed
});
If you don't care whether the getJSONs come back successfully or not and instead only care that they are done you can instead:
$.when(dfd1, dfd2).done( utility.messageBoxClose );
A variable
function render(url1, url2, message) {
utility.messageBoxOpen(message);
var isOneDone = false;
$.getJSON(url1, function (items) {
// Do something
if(!isOneDone)
isOneDone = true;
else
utility.messageBoxClose();
});
$.getJSON(url2, function (items) {
// Do something
if(!isOneDone)
isOneDone = true;
else
utility.messageBoxClose();
});
}
You can replace the getJSON() call to one using $.ajax which accomplishes the same thing but gives you more flexibility:
$.ajax({
url: http://whatever,
dataType: 'json',
async: false,
data: {data},
success: function(data) {
// do the thing
}
});
Note the async:false part - this makes the code execution pause until the request is completed. So you could simply make your two calls this way, and close the dialog after the second call is completed.
function render(url1, url2, message) {
utility.messageBoxOpen(message);
$.when($.getJSON(url1, function (items) {
// Do something
utility.messageBoxClose();
}), $.getJSON(url2, function (items) {
// Do something
})).then(function () {
//Both complete
});
}
jQuery.when