Implementing Callbacks / Deferred Objects with AJAX - javascript

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.

Related

Nesting ajax requests makes code hard to read

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)
})

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.

How to Stop execution of the JS Code after $.ajax call i.e. when server is processing the AJAX Request

I have a strange issue in JQuery AJAX..
My Steps sequence are as follows:
1) I have a JS Function which I am calling on Button Click Event:
function Click()
{
//I am performing some Validations then making an AJAX Request:
$.ajax({
type: "POST",
url: url,
context: window,
data: datatoPost,
contentLength: contentLength,
async: true,
success: function (response) {
callbackFn(response);
},
error: function (msg) {
this.error = msg;
}
});
// The callback function is called on Successfull AJAX Request
// i.e. callbackFn (See below)
// I am then checking the window.IsValid which I have set in below function;
if (window.IsValid == true) {
// Then Perform another AJAX Request
}
else {
// do nothing
}
}
function callbackFn(response)
{
if(response == 'Valid')
{
window.IsValid = true;
}
else
{
window.IsValid = false;
}
}
2) Now, The Problem is while server is processing the First AJAX Request then the code written after that i.e.
if (window.IsValid == true) {
// Then Perform another AJAX Request
}
else {
// do nothing
}
}
is executed
3) I get window.IsValid = false as first AJAX Request's callback function i.e. callbackFn(response) is not called yet and Even after a Valid response of first AJAX request my second ajax request is not getting executed as window.IsValid variable is not still got set in callback function as callback is not called yet due to server is processing the request.
Please help me I am stuck..
you should then use
async: false,
in you ajax function call. Which is not recomended. The better thing will be to use
if (window.IsValid == true) {
// Then Perform another AJAX Request
}
else {
// do nothing
}
}
within your callback function.
Because your post is asynchronous, the script continues to execute whilst the ajax request is being processed.
Instead, you need to move your tests for window.IsValid into the success function:
function Click()
{
//I am performing some Validations then making an AJAX Request:
$.ajax({
type: "POST",
url: url,
context: window,
data: datatoPost,
contentLength: contentLength,
async: true,
success: function (response) {
callbackFn(response);
if (window.IsValid == true) {
// Then Perform another AJAX Request
// best done as another function.
}
else {
// do nothing
}
},
error: function (msg) {
this.error = msg;
}
});
}

Javascript scoping query using jQuery $.ajax

I am trying to write simple function that checks to see if a designer name exists in the database. I am using jQuery's ajax function to try to do this:
function checkDesignerName(name)
{
var designer_name = $('input[name="name"]').val();
var designer_exists = false;
var temp = $.ajax( { type: "GET",
url: "/api/check_brand_exists/",
data : {name : designer_name },
success: function(data) {
designer_exists = $.parseJSON(data);
return designer_exists;
}}).statusText;
return designer_exists;
}
I have read about javascript scoping, and but still can't seem to find my bug, which is checkDesignerName always returns false. Do I need to use a closure for this function to work correctly?
Thanks
It's the nature of AJAX which is asynchronous that you seem to have troubles with understanding.
At this stage:
return designer_exists;
your AJAX call hasn't yet finished. It's only inside the success callback, which happens much later, that you can use the results. You cannot have a function which returns some result and this result depends on an AJAX call. You can only exploit the results of an AJAX call iniside the success callback:
function checkDesignerName(name)
{
var designer_name = $('input[name="name"]').val();
$.ajax({
type: "GET",
url: "/api/check_brand_exists/",
data : { name : designer_name },
success: function(data) {
var designer_exists = $.parseJSON(data);
// Here and only here you know whether this designer exists
alert(designer_exists);
}
});
}
You could of course perform a synchronous call to the server which is something totally not recommended as it will freeze the client browser and piss the user off your site during the AJAX request by setting the async: false flag:
function checkDesignerName(name)
{
var designer_name = $('input[name="name"]').val();
var designer_exists = false;
$.ajax({
type: "GET",
url: "/api/check_brand_exists/",
async: false,
data : { name : designer_name },
success: function(data) {
designer_exists = $.parseJSON(data);
}
});
return designer_exists;
}
I am mentioning this just for completeness of the answer, not as something that you should ever be doing.
Now because it seems that you are doing some kind of validation logic here, here's what I would recommend you as an ultimate solution: the jquery.validate plugin. It has this great remote rule support that will do exactly what you need here.
$.ajax is a async call. It means the statement return designer_exists gets executed even before success function is executed. That is the reason it always returns false.
your success function don't see designer_exists variable
return action runs before success function will run
You may run sync request or redesign code to callbacks logic.
For sync request your code will be:
var designer_exists = false;
function checkDesignerName(name)
{
designer_exists = false;
var designer_name = $('input[name="name"]').val();
$.ajax( { async:false,
type: "GET",
url: "/api/check_brand_exists/",
data : {name : designer_name },
success: function(data) {
designer_exists = $.parseJSON(data);
}}).statusText;
return designer_exists;
}
As Dimitrov correctly noted it's asynchronous. If you want to encapsulate the ajax call within the function you could pass in the success callback.
function checkDesignerName(name, successCallback)
and then you assign it to the jQuery ajax success function.

How can I get jQuery to perform a synchronous, rather than asynchronous, Ajax request?

I have a JavaScript widget which provides standard extension points. One of them is the beforecreate function. It should return false to prevent an item from being created.
I've added an Ajax call into this function using jQuery:
beforecreate: function (node, targetNode, type, to) {
jQuery.get('http://example.com/catalog/create/' + targetNode.id + '?name=' + encode(to.inp[0].value),
function (result) {
if (result.isOk == false)
alert(result.message);
});
}
But I want to prevent my widget from creating the item, so I should return false in the mother-function, not in the callback. Is there a way to perform a synchronous AJAX request using jQuery or any other in-browser API?
From the jQuery documentation: you specify the asynchronous option to be false to get a synchronous Ajax request. Then your callback can set some data before your mother function proceeds.
Here's what your code would look like if changed as suggested:
beforecreate: function (node, targetNode, type, to) {
jQuery.ajax({
url: 'http://example.com/catalog/create/' + targetNode.id + '?name=' + encode(to.inp[0].value),
success: function (result) {
if (result.isOk == false) alert(result.message);
},
async: false
});
}
You can put the jQuery's Ajax setup in synchronous mode by calling
jQuery.ajaxSetup({async:false});
And then perform your Ajax calls using jQuery.get( ... );
Then just turning it on again once
jQuery.ajaxSetup({async:true});
I guess it works out the same thing as suggested by #Adam, but it might be helpful to someone that does want to reconfigure their jQuery.get() or jQuery.post() to the more elaborate jQuery.ajax() syntax.
Excellent solution! I noticed when I tried to implement it that if I returned a value in the success clause, it came back as undefined. I had to store it in a variable and return that variable. This is the method I came up with:
function getWhatever() {
// strUrl is whatever URL you need to call
var strUrl = "", strReturn = "";
jQuery.ajax({
url: strUrl,
success: function(html) {
strReturn = html;
},
async:false
});
return strReturn;
}
All of these answers miss the point that doing an Ajax call with async:false will cause the browser to hang until the Ajax request completes. Using a flow control library will solve this problem without hanging up the browser. Here is an example with Frame.js:
beforecreate: function(node,targetNode,type,to) {
Frame(function(next)){
jQuery.get('http://example.com/catalog/create/', next);
});
Frame(function(next, response)){
alert(response);
next();
});
Frame.init();
}
function getURL(url){
return $.ajax({
type: "GET",
url: url,
cache: false,
async: false
}).responseText;
}
//example use
var msg=getURL("message.php");
alert(msg);
Keep in mind that if you're doing a cross-domain Ajax call (by using JSONP) - you can't do it synchronously, the async flag will be ignored by jQuery.
$.ajax({
url: "testserver.php",
dataType: 'jsonp', // jsonp
async: false //IGNORED!!
});
For JSONP-calls you could use:
Ajax-call to your own domain - and do the cross-domain call server-side
Change your code to work asynchronously
Use a "function sequencer" library like Frame.js (this answer)
Block the UI instead of blocking the execution (this answer) (my favourite way)
Note: You shouldn't use async: false due to this warning messages:
Starting with Gecko 30.0 (Firefox 30.0 / Thunderbird 30.0 / SeaMonkey 2.27), synchronous requests on the main thread have been deprecated due to the negative effects to the user experience.
Chrome even warns about this in the console:
Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help, check https://xhr.spec.whatwg.org/.
This could break your page if you are doing something like this since it could stop working any day.
If you want to do it a way that still feels like if it's synchronous but still don't block then you should use async/await and probably also some ajax that is based on promises like the new Fetch API
async function foo() {
var res = await fetch(url)
console.log(res.ok)
var json = await res.json()
console.log(json)
}
Edit
chrome is working on Disallowing sync XHR in page dismissal when the page is being navigated away or closed by the user. This involves beforeunload, unload, pagehide and visibilitychange.
if this is your use case then you might want to have a look at navigator.sendBeacon instead
It is also possible for the page to disable sync req with either http headers or iframe's allow attribute
I used the answer given by Carcione and modified it to use JSON.
function getUrlJsonSync(url){
var jqxhr = $.ajax({
type: "GET",
url: url,
dataType: 'json',
cache: false,
async: false
});
// 'async' has to be 'false' for this to work
var response = {valid: jqxhr.statusText, data: jqxhr.responseJSON};
return response;
}
function testGetUrlJsonSync()
{
var reply = getUrlJsonSync("myurl");
if (reply.valid == 'OK')
{
console.dir(reply.data);
}
else
{
alert('not valid');
}
}
I added the dataType of 'JSON' and changed the .responseText to responseJSON.
I also retrieved the status using the statusText property of the returned object. Note, that this is the status of the Ajax response, not whether the JSON is valid.
The back-end has to return the response in correct (well-formed) JSON, otherwise the returned object will be undefined.
There are two aspects to consider when answering the original question. One is telling Ajax to perform synchronously (by setting async: false) and the other is returning the response via the calling function's return statement, rather than into a callback function.
I also tried it with POST and it worked.
I changed the GET to POST and added data: postdata
function postUrlJsonSync(url, postdata){
var jqxhr = $.ajax({
type: "POST",
url: url,
data: postdata,
dataType: 'json',
cache: false,
async: false
});
// 'async' has to be 'false' for this to work
var response = {valid: jqxhr.statusText, data: jqxhr.responseJSON};
return response;
}
Note that the above code only works in the case where async is false. If you were to set async: true the returned object jqxhr would not be valid at the time the AJAX call returns, only later when the asynchronous call has finished, but that is much too late to set the response variable.
With async: false you get yourself a blocked browser.
For a non blocking synchronous solution you can use the following:
ES6/ECMAScript2015
With ES6 you can use a generator & the co library:
beforecreate: function (node, targetNode, type, to) {
co(function*(){
let result = yield jQuery.get('http://example.com/catalog/create/' + targetNode.id + '?name=' + encode(to.inp[0].value));
//Just use the result here
});
}
ES7
With ES7 you can just use asyc await:
beforecreate: function (node, targetNode, type, to) {
(async function(){
let result = await jQuery.get('http://example.com/catalog/create/' + targetNode.id + '?name=' + encode(to.inp[0].value));
//Just use the result here
})();
}
This is example:
$.ajax({
url: "test.html",
async: false
}).done(function(data) {
// Todo something..
}).fail(function(xhr) {
// Todo something..
});
Firstly we should understand when we use $.ajax and when we use $.get/$.post
When we require low level control over the ajax request such as request header settings, caching settings, synchronous settings etc.then we should go for $.ajax.
$.get/$.post: When we do not require low level control over the ajax request.Only simple get/post the data to the server.It is shorthand of
$.ajax({
url: url,
data: data,
success: success,
dataType: dataType
});
and hence we can not use other features(sync,cache etc.) with $.get/$.post.
Hence for low level control(sync,cache,etc.) over ajax request,we should go for $.ajax
$.ajax({
type: 'GET',
url: url,
data: data,
success: success,
dataType: dataType,
async:false
});
this is my simple implementation for ASYNC requests with jQuery. I hope this help anyone.
var queueUrlsForRemove = [
'http://dev-myurl.com/image/1',
'http://dev-myurl.com/image/2',
'http://dev-myurl.com/image/3',
];
var queueImagesDelete = function(){
deleteImage( queueUrlsForRemove.splice(0,1), function(){
if (queueUrlsForRemove.length > 0) {
queueImagesDelete();
}
});
}
var deleteImage = function(url, callback) {
$.ajax({
url: url,
method: 'DELETE'
}).done(function(response){
typeof(callback) == 'function' ? callback(response) : null;
});
}
queueImagesDelete();
Because XMLHttpReponse synchronous operation is deprecated I came up with the following solution that wraps XMLHttpRequest. This allows ordered AJAX queries while still being asycnronous in nature, which is very useful for single use CSRF tokens.
It is also transparent so libraries such as jQuery will operate seamlessly.
/* wrap XMLHttpRequest for synchronous operation */
var XHRQueue = [];
var _XMLHttpRequest = XMLHttpRequest;
XMLHttpRequest = function()
{
var xhr = new _XMLHttpRequest();
var _send = xhr.send;
xhr.send = function()
{
/* queue the request, and if it's the first, process it */
XHRQueue.push([this, arguments]);
if (XHRQueue.length == 1)
this.processQueue();
};
xhr.processQueue = function()
{
var call = XHRQueue[0];
var xhr = call[0];
var args = call[1];
/* you could also set a CSRF token header here */
/* send the request */
_send.apply(xhr, args);
};
xhr.addEventListener('load', function(e)
{
/* you could also retrieve a CSRF token header here */
/* remove the completed request and if there is more, trigger the next */
XHRQueue.shift();
if (XHRQueue.length)
this.processQueue();
});
return xhr;
};
Since the original question was about jQuery.get, it is worth mentioning here that (as mentioned here) one could use async: false in a $.get() but ideally avoid it since asynchronous XMLHTTPRequest is deprecated (and the browser may give a warning):
$.get({
url: url,// mandatory
data: data,
success: success,
dataType: dataType,
async:false // to make it synchronous
});

Categories