Keeping AJAX Requests Serial in jQuery - javascript

I have some code that I recently moved to FastCGI for a backend to fulfill AJAX requests from a jQuery based front end. The problem is that while FastCGI largely accelerates it, I actually get a negative performance issue from two jQuery AJAX requests hitting it in too quick of succession. What I'd like to do is "lock" AJAX so that the requests happen in serial rather than parallel -- each request only takes around 180ms to perform, but if the second request goes in before the first one has completed, the second request ends up taking about a second and a half instead.
I suppose the obvious way that I could make it serial is to put an $.ajax request inside the .done portion of the previous request, but the requests are in to different functions and need to stay that way since they don't always need to be fired together -- just frequently so. Imagine this:
function loadCategories () {
// Do some organizing stuff, set url, etc.
$.ajax({
url: url,
data: parameters,
type: "GET",
dataType : "json",
})
.done(function( json ) {
//process sidebar category data.
});
}
function loadArticles () {
// Do some organizing stuff, set url, etc.
$.ajax({
url: url,
data: parameters,
type: "GET",
dataType : "json",
})
.done(function( json ) {
//process article data.
});
}
Both of these functions are called in a third function:
function loadPage (parameters) {
loadCategories(parameters);
loadArticles(parameters);
}
What I'd like to do is keep the .done and .always logic within those two functions, but also return a promise from these functions so that I could then use $.then to keep loadArticles from firing until loadCategories has completed. I've tried to figure out the right way to do this, but haven't succeeded in the right way to do this yet.

Have the functions return the Deferred object that $.ajax() returns, and then you can wait for this in the calling function.
function loadCategories() {
// Do some organizing stuff, set url, etc.
return $.ajax({
url: url,
data: parameters,
type: "GET",
dataType: "json",
})
.done(function(json) {
//process sidebar category data.
});
}
function loadArticles() {
// Do some organizing stuff, set url, etc.
return $.ajax({
url: url,
data: parameters,
type: "GET",
dataType: "json",
})
.done(function(json) {
//process article data.
});
}
function loadPage(parameters) {
$.when(loadCategories(parameters)).then(
function() {
loadArticles(parameters)
};
});
}

Related

How to asynchronize multiple Ajax Post call to the same URL

I've an array of items in javascript, and for each item, I've to make an ajax post to a URL to get corresponding info and display the info in a container. The code broadly looks like this:
var data = some_data;
array.forEach(item, idx)=> {
callAjaxAndUpdate(data, item, $('div#container'+i);
});
and the Ajax method is simply
var standardUrl = 'https://example.com/post.php';
function callAjaxAndUpdate(data, item, container) {
$.ajax({
url: standardUrl
data: data,
type: 'POST',
}).done(function(res) {
container.append(res.data);
}).fail(function(res) {
container.append(res.responseText);
}).always(function() {
container.append('DONE!');
});
}
However, this thing whole thing seems to have become blocking. I did server-side logging of timestamps, and I could see that ajax request for each item is getting triggered only after that for the previous one has completed (always section executed).
Could someone help me out with why this setup is synchronous, and how to make it async? Please note that this script is executing in the browser, and not on nodeJS.
EDIT: Turns out, it's the PHP backend which is processing the requests in a blocking way. Perhaps the session is the culprit here. I would close the question now. Thanks for your help and suggestions.
Try default Ajax async
var standardUrl = 'https://example.com/post.php';
function callAjaxAndUpdate(data, item, container) {
$.ajax({
url: standardUrl,
async: true,
data: data,
type: 'POST',
}).done(function(res) {
container.append(res.data);
}).fail(function(res) {
container.append(res.responseText);
}).always(function() {
container.append('DONE!');
});
}
Or you can call the method on .done method

Using .promise() to have the Ajax request start first?

I was advised to use .promise() in this function because as it is now, the Ajax call won't start until the loading animation has finished. I read through the codex, but couldn't really understand how to implement it. Can someone show me how it's done?
function projectShow() {
$('#loading-animation').show(100, function() {
$.ajax({
type: 'POST',
cache: false,
url: ajaxURL,
data: {'action': 'load-content', post_id: post_id },
success: function(response) {
$('#project-container').slideDown('fast').html(response);
$('#loading-animation').hide();
return false;
}
});
});
}
The advice to use .promise() was probably given because your code forces a small 100ms delay before making the AJAX call (while the animation runs to completion). It may make more sense to make the AJAX call, then start the animation while waiting for the response.
I have modified your code to achieve this by simply making the AJAX call before starting the animation. (jQuery.ajax() uses .promise() internally by default. See the async option for more information.) Be aware that in cases where the AJAX call returns in less than 100ms, the execution order of your JavaScript may be altered. This may cause undesired side-effects, but I think it is OK in your case.
function projectShow() {
$.ajax({
type: 'POST',
cache: false,
url: ajaxURL,
data: {'action': 'load-content', post_id: post_id },
success: function(response) {
$('#project-container').slideDown('fast').html(response);
$('#loading-animation').hide();
return false;
}
});
$('#loading-animation').show(100);
}

Mockjax dynamic mock stops working with `dataType="Script"`

I have a dynamic mock setup using mockjax, and it works for most of my ajax requests, but fails when the dataType is set to Script, and lets the request fall through to regular Ajax handler.
// gets mocked
$.ajax({
type: "GET",
url: "http://myurl.com/myfile.js?_=1395314460347"
})
// does not get mocked!
$.ajax({
type: "GET",
dataType: "script",
url: "http://myurl.com/myfile.js?_=1395314460347"
})
How can I configure dynamic mocks in mockjax to intercept requests with the dataType set?
UPDATE: example code for mockjax definition
I am creating dynamic mock, so I am defining via function, not plain object, something like this...
$.mockjax(function(settings) {
// settings.url == '/restful/<service>'
var service = settings.url.match(/\/restful\/(.*)$/);
if ( service ) {
return {
proxy: '/mocks/' + service[1] + '.json',
// handle `dataType: 'script'`
dataType: 'application/javascript'
};
}
return;
});
This appears to be a bug with how Mockjax handles crossDomain script requests. It is not doing anything special to detect the crossDomain request (like it does with JSONP) and as such, when it passes the request back to the original $.ajax method – jQuery never uses the mocked up XHR object it was provided by Mockjax.
So in essence, Mockjax is intercepting the request, and then passes it right back to jQuery and it fails on you.
I opened an issue here so this can be fixed: https://github.com/appendto/jquery-mockjax/issues/136
In the mean time you have a two choices. If you want to quickly patch mockjax, add this line to around 471:
origSettings.crossDomain = false;
That section will look like this when you are done:
mockHandler.cache = requestSettings.cache;
mockHandler.timeout = requestSettings.timeout;
mockHandler.global = requestSettings.global;
origSettings.crossDomain = false;
copyUrlParameters(mockHandler, origSettings);
The other alternative (which I recommend against), is adding crossDomain: false to your actual AJAX request. I don't recommend this due to the need to remove that line when you remove your mocks later.
Thanks #Nicholas Cloud for pinging me and bringing this issue to my attention.
Are you setting the dataType property in your mocked endpoints?
See: https://github.com/appendto/jquery-mockjax#data-types
If you are, have you tried setting the mock dataType to application/javascript?
$.mockjax({
type: "GET",
dataType: "application/javascript",
url: "myfile.js?_=1395314460347",
responseText: "(function () { alert('hello world!'); }());"
});
$.ajax({
type: "GET",
dataType: "script",
url: "myfile.js?_=1395314460347"
});

Wait for Async ajax to complete before moving onto other code?

I know this has been asked, probably, a million times, but for the life of me I cannot get anything to work.
I have a UI wizard control that on the "changed" event validates the model. If the model is not valid, it doe not allow the user to move forward in the wizard. I have tired using the $.when().done() feature in jquery, but my code still passes through before making sure the model is valid. The reason for calling an async ajax request is I do not want the UI to lock up so I can show some sort of progress indicator. I had set the async property to false, but my UI indicator would never show up. Here is an example of what my code is doing:
//the change event that is called when the user clicks 'next' on the wizard:
wizard.on('change', function (e, data) {
var isValid = $.validate({
"Model": [The_UI_MODEL],
"Url": [URL_To_Server_Validation],
"Async": true, //tells ajax request to send as async
});
//Tells the wizard not to move 'next' if the request comes back as not valid
if (data.direction === 'next' && !isValid) {
e.preventDefault();
}
}
//I am using the $.extend method for JQuery to create a function that will validate any model in my system.
validate: function(options) {
//Clear any previous validation errors
$.clearValidations();
var data = $.postJson(options);
//the result is getting returned before the $.postJson(options) finishes
return data.Success;
}
//I created my own method that extends the $.ajax method so I could do other things before /after a request:
postJson: function(options){
...other code for my application
//This is where I want the ajax request to happen and once done return the data coming back
//This is what I have tried, but it is not doing what I thought.
$.when(function(){
return $.ajax({
url: options.Url,
type: 'POST',
cache: false,
async: options.Async,
timeout: options.Timeout,
contentType: 'application/json; charset=utf-8',
dataType: "json",
data: JSON.stringify(options.Model),
error: function(xhr, status, error) {
...do stuff if ajax errors out
},
success: function (data) {
},
});
}).done(function(response){
//looks like i get back the responseText of the request. which is fine, but other posts i have read stated i should be getting back the request itself
...other UI stuff
return response;
})
}
KarelG is absolutely right. You need to refactor your code and do your valdiation check within the success callback of the ajax request.
Something like this...
wizard.on('change', function (e, data) {
$.ajax({
url: [URL_To_Server_Validation],
type: 'POST',
cache: false,
async: true,
contentType: 'application/json; charset=utf-8',
dataType: "json",
data: {"Model": [The_UI_MODEL]},
success: function (response) {
//Tells the wizard not to move 'next' if the request comes back as not valid
if(!response & data.direction === 'next')
{
e.preventDefault();
}
}
});
});
It looks like you're trying to write asynchronous code as if it were synchronous. An asynchronous call such as your $.validate() will return immediately without a result and continue on to the rest of your code. Anything you want to happen when the validate call finishes must be done in a callback function passed to it.
You can use jQuery promises (when, then, done, etc.) or another library such as async.js to help manage the control flow.
Also, this isn't particularly useful now since there's little to no browser support for it yet, but the yield operator plus a library such as Task.js will eventually let us write asynchronous code as if it were synchronous.

Can't get JQuery $.ajax() to work entirely synchronously

UPDATE Following #Ryan Olds suggestion to include the setTimeout in the callback, I must clarify that in my production code I'm calling multiple urls to get json data from several sites. (Have updated JavaScript code below).
Is it only possible to have multiple timeouts scattered throughout this function?
I have a self-invoking updateFunction as follows:
(function update() {
$.ajax({
type: 'GET',
url: "http://myexample.com/jsondata",
dataType: 'json',
success: function (data) {
// do some callback stuff
},
async: false
});
$.ajax({
type: 'GET',
url: "http://myexample2.com/jsondata2",
dataType: 'json',
success: function (data) {
// do some further callback stuff
},
async: false
});
setTimeout(update, 2000);
})();
What I expected this code to do
I hoped that this function would go off to the target URL and wait for the result, then deal with the success callback. Then (and only then) would it fall through to set a 2 second timeout to call the function again.
What appears to be happening instead
Instead, the GET request codes out, and before the response has been dealt with, the timeout has already been set.
What am I missing? How can I make this entirely synchronous?
If I were you, I'd make use of jQuery's support for deferred action.
(function update() {
$.when($.ajax({
type: 'GET',
url: "http://myexample.com/jsondata",
dataType: 'json',
success: function (data) {
// do some callback stuff
}
}), $.ajax({
type: 'GET',
url: "http://myexample2.com/jsondata2",
dataType: 'json',
success: function (data) {
// do some further callback stuff
}
}), $.ajax({
// more requests as you like
})).then(function() {
// when all the requests are complete
setTimeout(update, 2000);
});
}());
Much nicer, IMHO, than mucking around with synchronous requests. Indeed, if the requests are cross-domain, this is pretty much your only option.
See
$.when
deferred.then
Move the timeout in to the success callback. The request is synchronous, it would appear the the callback is not.
I would modify the setup like so:
function update() {
$.ajax({
type: 'GET',
url: "http://myexample.com/jsondata",
dataType: 'json',
success: function (data) {
// do some callback stuff
},
async: false
});
$.ajax({
type: 'GET',
url: "http://myexample2.com/jsondata2",
dataType: 'json',
success: function (data) {
// do some further callback stuff
},
async: false
});
}
setInterval(update, 2000);
update(); // only necessary if you can't wait 2 seconds before 1st load.
You cannot make it entirely synchronous because you're setting up calls to alternate domains. That's done (internal to jQuery) by creating <script> tags and adding them to the document. Browsers perform those calls asynchronously, and that's that. You can't make ordinary xhr requests to domains different from your own.
I can't imagine why you'd want something like that to be synchronous, especially since you're doing many of these operations.
I don't think async: false works on cross domain requests.
From the docs:
async Boolean
Default: true
By default, all requests are sent asynchronously (i.e. this is set to true by default). If you need synchronous requests, set this option to false. Cross-domain requests and dataType: "jsonp" requests do not support synchronous operation. Note that synchronous requests may temporarily lock the browser, disabling any actions while the request is active.
In any case, maybe you can set some conditionals to fire the requests in the order that you want.

Categories