Nesting ajax requests makes code hard to read - javascript

I usually like to organize my code so that one function fires a bunch of other
functions, like this:
/**
* GET MESSAGES:
*/
$(function() {
$.ajax({
url: '/messages',
method: 'GET',
dataType: 'json',
success: function(messages) {
if (messages.length > 0) {
keyedMessages = keyFork(messages);
reversedMessages = reverse(keyedMessages);
crushedMessages = crush(reversedMessages);
getFriendships(messages, crushedMessages);
}
mail.template.airmail();
}
});
});
However, if I need to do a second Ajax request inside one of the nested
functions I can't return the data because of the scope of the Ajax request
and it makes my code inconsistent and hard to follow, sort of broken up all over the place. For example, if one of the functions
invoked above fires a second Ajax request for friendships anything I write
after that will be broken from the communication chain due to the request and it seems impossible to return anything:
/**
* GET FRIENDSHIPS:
*/
function getFriendships(messages, crushedMessages) {
$.ajax({
url: 'friendships',
method: 'get',
dataType: 'json',
success: function(friendships) {
addKey(crushedMessages, friendships);
filteredCrushedMessages = filterUnconfirmedSender(crushedMessages);
filteredCrushedMessages.forEach(function(filteredCrushedMessage) {
mail.sidebar.builder.messengers(filteredCrushedMessage);
});
mail.snailMail.onload();
}
});
}
If I try to return the data it doesn't work. Consequently I'll have to
continue invoking functions inside the nested request, every time I need to make another nested ajax request it breaks the chain. This makes my
code very hard to read. Are there any solutions to this problem or is
code that uses Ajax requests just hard to read?

You could store the data on a DOM element, then use jQuery Custom Events to get it done.
There's even support for passing arguments to your event handler:
https://learn.jquery.com/events/introduction-to-custom-events/#naming-custom-events

If I try to return the data it doesn't work.
Not appear jQuery promise returned from either function at Question ?
Try utilizing return statement , $.when.apply(this, arrayOfPromises) to return array of jQuery promise object from getFriendships
function getFriendships(messages, crushedMessages) {
return $.ajax({
url: 'friendships',
method: 'get',
dataType: 'json',
success: function(friendships) {
addKey(crushedMessages, friendships);
filteredCrushedMessages = filterUnconfirmedSender(crushedMessages);
mail.snailMail.onload();
return $.when.apply($
, filteredCrushedMessages.map(function(filteredCrushedMessage) {
return mail.sidebar.builder.messengers(filteredCrushedMessage);
})
);
}
});
}
// e.g.,
getFriendships(messages, crushedMessages)
.then(function success() {
console.log(arguments)
}, function err(jqxhr, textStatus, errorThrown) {
console.log(errorThrown)
})

Related

Using JSON data retrieved via AJAX in separate function

Apologies if this is a duplicate question, I've followed some steps from another question which didn't seem to help me. I am trying to retrieve some JSON data, store part of the data into a variable and use that variable in a separate function outside of the AJAX request.
My expected response from the json data is http://localhost:8000/tutorials/retrieve/?page=2 (This response shows if I log the variable inside of the AJAX code) however the actual response I get when I try to log the variable from another function is as follows:
n.Event {originalEvent: MouseEvent, type: "click", timeStamp: 1436727171161, jQuery21304066238570958376: true, toElement: div#loadmore.recentTutorials…}
Here is the current code
var recentFirstPage = '';
function retrieveTutorials(){
$.ajax({
type: "GET",
url: "/tutorials/retrieve",
dataType: "json",
success: function(data){
**some unrelated parsing code here**
//Set the variable to what I need
recentFirstPage = data.next_page_url;
},
error: function() {
alert("An error occurred processing AJAX request.");
}
});
}
$('#main-content-wrap').on('click', '.recentTutorials', function(recentFirstPage){
//Does not return expected result
console.log(recentFirstPage);
});
When I click .recentTutorials I expect the console to log the data from JSON however it doesn't. Can someone help clear up my error(s)?
The reason that it doesn't log the data from JSON s that the call is asynchronous. This means that the function will execute top to bottom without waiting for the call to finish.
One method that's used is to leverage deferred objects which return a promise on completion. You can accept an anonymous function to the invoker function so that it's call back is executed within the scope of the click.
Observe:
function retrieveTutorials(){
return $.ajax({
type: "GET",
url: "/tutorials/retrieve",
dataType: "json"
});
}
$('#main-content-wrap').on('click', '.recentTutorials', function(){
//store our function call as an ajax promise
var promise = retrieveTutorials();
//wait for the ajax to return so we can mutate the data
promise.done(function(data){
//now our data will be properly
recentFirstPage = data.next_page_url;
});
});
It seems to me that you are trying to log the data before the ajax is completed. It`s better to use deferreds . Try this:
function retrieveTutorials(){
return $.ajax({ // will return deferred object
type: "GET",
url: "/tutorials/retrieve",
dataType: "json",
success: function(data){
**some unrelated parsing code here**
//Set the variable to what I need
recentFirstPage = data.next_page_url;
},
error: function() {
alert("An error occurred processing AJAX request.");
}
});
}
$.when( retrieveTutorials() ).done(function ( data ) {
console.log(recentFirstPage);
});
The parameter in your click handler is the last and final nail in your coffin. It's always the jquery event and you shouldn't handle it at all.
You do need to call the retrieveTutorials() function within the handler and you need to pass it a callback function that will be executed on success. So your retrieveTutorials() function will look something like this:
function retrieveTutorials(success){
$.ajax({ type: "GET", url: "/tutorials/retrieve",
dataType: "json",
success: success,
error: function() { alert("An error occurred processing AJAX request.");
} }); }
And your click handler:
$('#main-content-wrap').on('click', '.recentTutorials', function(){
retrieveTutorials(function(data){
console.log(data.next_page_url);
});
});
You can also use all the promise based goodness in the other anwers, but the above would be an idiom you'll see again and again.

prevent continue for loop when jquery ajax until load

I have a for loop in java script :
for(i=1;i<=10;i++){
$.ajax({
type: "GET",
url: "insert_names.php",
success: function (data) {
// some codes
}
});
}
this script send all of ajax request Simultaneously !
But It makes problem for me ...
how can i can prevent continue for loop when ajax is working ?
If you need the requests to be executed in serial instead of parallel, you can do that with a little re-structuring. Don't make the AJAX requests synchronous, just use the response of each request to invoke the next one.
Consider a structure like this:
function sendRequest() {
$.ajax({
type: "GET",
url: "insert_names.php",
success: function (data) {
// some codes
}
});
}
Now the request is wrapped in a function that you can invoke. So invoke it in response to the previous request:
function sendRequest() {
$.ajax({
type: "GET",
url: "insert_names.php",
success: function (data) {
// some codes
sendRequest();
}
});
}
It's starting to look like a standard recursive pattern, so all you need now is a terminating condition. Similar to the original loop, just use an incrementing value:
var i = 1;
function sendRequest() {
$.ajax({
type: "GET",
url: "insert_names.php",
success: function (data) {
// some codes
i++;
if (i <= 10) {
sendRequest();
}
}
});
}
Then just invoke it once to start it:
sendRequest();
Now the request should be invoked 10 times as expected (unless my math is off by one, but that should be easy to correct), each request happening in response to the previous request.
With asynchronous programming, don't try to make it synchronous. Instead, perform actions in response to the callbacks.

How to handle simultaneous ajax responses using single ajax method?

Method for handling ajax
function ajaxMethod(){
return $.ajax({
url: ajaxUrl,
type: "POST",
dataType: "JSONP",
jsonpCallback: ajaxCallback
});
}
Calls to this method:
dD(ajaxMethod());
aA(ajaxMethod());
bB(ajaxMethod());
cC(ajaxMethod());
aa,bb,cc,dd method have
promise.success(function (response) {
console.log(response);
});
Now aA response is coming in bB function,,bb response in cc function and as simultaneous call is coming.
Also tried using async true nothing happens.
Any suggestions?
With jsonpCallback you are telling jQuery to use a specific function name for the callback function (instead of generating a unique one), so each time you call it, you overwrite the previous function.
Just remove jsonpCallback: ajaxCallback.
While you are at it, remove type: "POST",, it is incompatible with JSONP.
I think this is what you are after.
This code is using the returned promise to wait for the result then passing the result to your other function.
ajaxMethod().success(function(response)
{
dD(response);
});
ajaxMethod().success(function(response)
{
aA(response);
});
ajaxMethod().success(function(response)
{
cC(response);
});
ajaxMethod().success(function(response)
{
dD(response);
});
Your aA, bB, cC and dD methods can now be:
function <insertname>(response)
{
console.log(response);
}
If you want your request to come in the synchronous way, then try the following :
var callBack = $.Callbacks();
callBack.add(dD(ajaxMethod()));
callBack.add(aA(ajaxMethod()));
callBack.add(bB(ajaxMethod()));
callBack.add(cC(ajaxMethod()));
callBack.fire();
the above line of code will make sure the respective ajax call would get call.

Implementing Callbacks / Deferred Objects with AJAX

Below is a function that is called when a user clicks the "Save" button on a form. When the PreSaveAction function returns true, the form will be submitted. If false is returned, nothing will happen. I'm using an AJAX call to here to validate form values and would like to have PreSaveAction return true if validateUniqueStaff succeeds, and vise versa if it fails.
function PreSaveAction() {
$.ajax({
url: listUrl,
type: "GET",
dataType: "json",
success: validateUniqueStaff,
error: function (data) {
alert("Error: Problem with the AJAX request");
}
});
//if (validateUniqueStaff succeeds) return true, else return false
}
My trouble is that I can't figure out how to incorporate a deferred object here. I've tried running the ajax call synchronously instead, which works in Chrome, but not in IE8 (a requirement).
I'm absolutely stumped. Any advice would be hugely appreciated! Let me know if I can provide any other information.
It is actually possible!
You need to use the developer tools (F12) to get the current java script code behind the button.
Here's how to proceed next:
//1. unbind the current handler:
var saveButton = ...; //your id (looks like ctl00_***)
var saveButtonCallBack = ...; //call back id (looks like ctl00$...)
//2. When document is ready
jQuery(saveButton).unbind('click').click(function(){
PreSaveActionPeter(
function (){
WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(saveButtonCallBack, "", true, "", "", false, true))
}
)
});
//3. Define your function:
function PreSaveActionPeter(callBack)
{
jQuery.ajax({
url: *your url*,
method: "GET",
headers: {
"accept": "application/json;odata=verbose",
},
dataType: "json",
success: function (data) {
if (data.d.results.length > 0) {
callBack();
}
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert(XMLHttpRequest.responseText);
}
});
return true;
}
//4. Override of the default PreSaveAction
function PreSaveAction()
{
return false;
}
Your PreSaveAction() is asynchronous. You can't return a success or failure value from that function. It will return long BEFORE the ajax call has completed.
For asynchronous calls (such as Ajax calls), you HAVE to process the result in a callback that gets called sometime later. You can't pretend to use synchronous coding techniques with an asynchronous operation. It just won't work. PreSaveAction() returns long before the ajax call is done so it's return value will not know the end result of the Ajax call.
If your SharePoint environment requires PreSaveAction() to return true/false and you need to do an Ajax operation in order to figure out if you should return true or false, then you're in a bind. You could make the Ajax call be synchronous, but that's generally a horrible user experience because it locks up the browser during the Ajax call and if you're going cross domain, you can't do synchronous anyway.
The best solution would be to understand what your options are with the PreSaveAction() function and see if you can pass a callback into it that will be called with the final result when the asynchronous Ajax call is done.
That would work something like this:
function PreSaveAction(callback) {
$.ajax({
url: listUrl,
type: "GET",
dataType: "json",
success: function(data) {
var retVal = validateUniqueStaff(data);
// call the callback to report the results of validation
callback(retVal);
},
error: function (data) {
alert("Error: Problem with the AJAX request");
callback(false);
}
});
}
The little bit of searching I did on PreSaveAction() in SharePoint indicates that it has to be synchronous. It was designed for client-side validation only (without Ajax calls to the server). To use it, you will have to return true or false directly from it and thus you can't use asynchronous Ajax calls in your PreSaveAction() implementation because the result won't be known in time. You might be able to make your Ajax call synchronous (which I hate because it can be a bad user experience) or you need to find a different way to do your server-assisted validation.
For form validation, it has been standard practise since time immemorial to issue form.submit() from a submit handler on successful validation, and to return false unconditionally.
Presumably, in the SharePoint environment, you can do the same but with the added minor convolution, in this case, that validation be undertaken only after successful receipt of an AJAX response.
Exactly what you write depends on what your validateUniqueStaff returns.
Assuming validateUniqueStaff to return a boolean (valid/invalid), you would write:
function PreSaveAction() {
var form = this;
//disable Save button here
$.ajax({
url: listUrl,
type: "GET",
dataType: "json"
}).then(validateUniqueStaff, function(jqXHR, textStatus, errorThrown) {
return new Error(textStatus);
}).then(function(valid) {
if(valid) {
form.submit();
} else {
return $.Deferred.reject(new Error('Validation failure')).promise();
}
}).fail(function(e) {
alert(e.message);//this will be either the AJAX error message or the validation failure message
}).always(function() {
//re-enable Save button here
});
return false;
}
Or (more elegantly from PreSaveAction's point of view), assuming validateUniqueStaff to return a Promise that is resolved on success or rejected on failure, you would write :
function PreSaveAction() {
var form = this;
//disable Save button here
$.ajax({
url: listUrl,
type: "GET",
dataType: "json"
}).then(validateUniqueStaff, function(jqXHR, textStatus, errorThrown) {
return new Error(textStatus);
}).then(form.submit, function(e) {
alert(e.message);//this will be either the AJAX error message or the validation failure message
}).always(function() {
//re-enable Save button here
});
return false;
}
I really don't think you should be considering synchronous AJAX, which is as unreliable as it is unnecessary.

Ajax jquery synchronous callback success

I have this function that makes an ajax call. I'm describing the problem in the last chunk of code comments.
function doop(){
var that = this;
var theold = "theold";
var thenew = "thenew";
$.ajax({
url: 'doop.php',
type: 'POST',
data: 'before=' + theold + '&after=' + thenew,
success: function(resp) {
if(resp == 1) {
$(that).siblings('.theold').html(thenew);
}
}
});
// I have some code here (out of the ajax) that **further** changes
// the .theold's html beyond what it was changed inside ajax success
// but the change depends on whether the resp (inside the success
// function) returned 1 or not, so this code out here depends on the ajax
// so it looks like I have to turn this ajax call into a sync ajax
return false;
}
Based on the problem as described in the code comments, what changes are best for this situation?
You need to set async: false for synchronous requests like this:
function doop(){
var that = this;
var theold = $(this).siblings('.theold').html();
var thenew = $(this).siblings('.thenew').val();
$.ajax({
async: false,
url: 'doop.php',
type: 'POST',
data: 'before=' + theold + '&after=' + thenew,
success: function(resp) {
if(resp == 1) {
$(that).siblings('.theold').html(thenew);
}
}
});
// some other code
return false;
}
see here for details
Either set the Ajax call to synchronous as stefita pointed out, or just move your code into the success callback. Why can't you do this? Even if it's another Ajax call it still can be done - you can nest them. With the information given by you so far (I can't see the problematic code, nor I have enough domain knowledge about your project) I don't see a problem, really.
I prefer to use callback to do the job because it achieves exactly the same result without actually making it synchronous. I use success:callback and then pass in the callback as a parameter.
function getData(callback) {
$.ajax({
url: 'register/getData',
data: "",
dataType: 'json',
success: callback
});
}
I then call this function like this:
getData(function(data){
console.log(data); //do something
});

Categories