I have this simple code:
function dlgEditPhase_okClicked(dlgEditPhase, event) {
$.post("/overview/phase/"+dlgEditPhase.projectId,
JSON.stringify({
"phaseName": dlgEditPhase.phaseName,
"begin": dlgEditPhase.begin,
"end": dlgEditPhase.end
}),
function(data) {
dlgEditPhase.close();
location.reload();
},
"json"
).fail(function(data) {
alert(data.responseJSON);
});
}
Problem is, the fail is always fired, even when the POST call returns code 200 or 202.
What am I doing wrong?
Fail can also fire because response is not a valid json
Related
I have a single-page app that uses a queuing mechanism based on promise, something like this:
a) a function that handles ajax requests
function AjaxSender(SomeAjaxData, FunctionToCallBack, SomeCallBackData) {
return $.ajax({
url: ...,
type: "POST",
data: SomeAjaxData,
success: function (msg, textStatus, request) {
if (FunctionToCallBack) {
FunctionToCallBack(SomeCallBackData);
//problem if there's a bug when this executes
}
}
});
}
b) a function that uses a promise object to queue requests
var AppAjaxPromise;
function AjaxRequestQueue(SomeAjaxData, FunctionToCallBack, SomeCallBackData) {
if (AppAjaxPromise) {
AppAjaxPromise = AppAjaxPromise.then(function () {
return AjaxSender(SomeAjaxData, FunctionToCallBack, SomeCallBackData);
});
return AppAjaxPromise;
}
AppAjaxPromise = AjaxSender(SomeAjaxData, FunctionToCallBack, SomeCallBackData);
return AppAjaxPromise;
}
When I want to send an ajax request, I call AjaxRequestQueue(TheAjaxData, TheFunctionToCallBack, TheCallBackData) and the queuing mechanism ensures that if multiple requests are sent simultaneously, or before one has finished returning, they are queued and processed one after the previous one is done.
The problem occurs when a bug stops the execution of the callback function. If that function bugs, the whole queuing mechanism stops and calling AjaxRequestQueue doesn't trigger ajax requests any more.
What do I need to do to fix this?
As jQuery's $.ajax returns a promise (and since you are using it), abandon the use of the success callback. Instead move that code in a then callback. This will allow you to chain a catch method (jQuery 3.x) call to it to respond to errors. If you don't trigger another error in that catch callback, the promise it returns will be resolved again (not rejected), so the rest of your chain will not be aborted:
function ajaxSender(someAjaxData, functionToCallBack, someCallBackData) {
return $.ajax({
url: ...,
type: "POST",
data: someAjaxData
}).then(function (msg, textStatus, request) {
if (functionToCallBack) {
functionToCallBack(someCallBackData);
}
}).catch(function (err) {
console.log('error occurred, but request queue will not be interrupted', err);
});
}
jQuery 2.x
The above needs jQuery 3.x. In jQuery versions before 3.x, you can replace the catch method like this (notice the null argument):
...
}).then(null, function (err) {
...
...but jQuery 2.x promises are not Promise/A+ compliant, which makes it a pain to get it right. Here is how you could do it for jQuery 2.x. This snippet uses a URL that mimics a delay and an HTTP response status code, which allows it to test request errors, JavaScript run time errors, and sequencing:
function ajaxSender(someAjaxData, functionToCallBack, someCallBackData) {
return $.ajax({
// URL for demo: server will use the sleep parameter in the data,
// and will return the given HTTP status
url: "http://httpstat.us/" + someAjaxData.status,
type: "GET", // The demo URL needs a GET
data: someAjaxData
}).then(function (data) {
if (functionToCallBack) {
try { // Would not be necessary if jQuery 2.x were Promise/A+ compliant
functionToCallBack(someCallBackData);
} catch (e) {
console.log(someCallBackData, 'Error occurred during callback');
}
}
}, function (err) { // This second function captures ajax errors
console.log(someCallBackData, 'HTTP error');
// Return a resolved promise.
// This would not be necessary if jQuery 2.x were Promise/A+ compliant
return $.when();
}); // In jQuery 3.x you would chain a catch call here instead of the try/catch.
}
var appAjaxPromise = $.when();
function ajaxRequestQueue(someAjaxData, functionToCallBack, someCallBackData) {
appAjaxPromise = appAjaxPromise.then(function () {
return ajaxSender(someAjaxData, functionToCallBack, someCallBackData);
});
return appAjaxPromise;
}
// Demo: the ajax data argument is also used to define the HTTP response status and
// the sleep time, and the data argument identifies the number of the call
// Survive an HTTP error
ajaxRequestQueue({ status: 404, sleep: 1000 }, myCallBack, 1);
// Survive a runtime error in the callback
ajaxRequestQueue({ status: 200, sleep: 2000 }, myErrorGeneratingCallBack, 2);
// Demo that the callback calls remain in the right order
ajaxRequestQueue({ status: 200, sleep: 3000 }, myCallBack, 3);
ajaxRequestQueue({ status: 200, sleep: 2000 }, myCallBack, 4);
ajaxRequestQueue({ status: 200, sleep: 1000 }, myCallBack, 5);
function myCallBack(data) {
console.log(data, "My callback is called");
}
function myErrorGeneratingCallBack(data) {
console.log(data, "My callback is called");
throw "I threw an error in my callback";
}
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
You can still continue the above pattern when moving to jQuery 3: it will still work. But ideally, you should then migrate the code to the catch-based version that I provided at the top.
Some other remarks
There is a consensus to only capitalise the first letter of a variable when it is a constructor/class.
By initialising appAjaxPromise as an immediately resolved promise, you can avoid code repetition:
var appAjaxPromise = $.when();
function ajaxRequestQueue(someAjaxData, functionToCallBack, someCallBackData) {
appAjaxPromise = appAjaxPromise.then(function () {
return ajaxSender(someAjaxData, functionToCallBack, someCallBackData);
});
return appAjaxPromise;
}
I am not sure if this answer will fix it, but the callbacks you are using in the success function might not be accessible from there.
You can add extra data to the request like so... and it will be accessible with 'this....'. (see success).
Not sure if you should ether :p I have been doing this to pass data from inside an object without having to change the ajax' context or using $.proxy. Also I have been able to access an object's function that fires a request, from inside that request's success, making it recursive for sending files in chunks.
If there are any comments on doing this, I would love to hear.
return $.ajax({
FunctionToCallBack: FunctionToCallBack,
SomeCallBackData: SomeCallBackData,
url: ...,
type: "POST",
data: SomeAjaxData,
success: function (msg, textStatus, request) {
if (this.FunctionToCallBack) {
this.FunctionToCallBack(this.SomeCallBackData);
//problem if there's a bug when this executes
}
}
});
So when handling, for example, the success data in jquery, should you check if the return data has the necessary data like this:
success: function (data) {
if (data.new_rank !== undefined) {
$('._user_rank').html(data.new_rank);
}
}
Or let it fail when it is not present?
success: function (data) {
$('._user_rank').html(data.new_rank);
}
in the previous example you can check if something has changed and needs to be fixt because of the error.
What approach is the best?
It's better you check it, for other code that may be you have in complete or other event. If you didn't, they will not run after error. You can check it this too:
success: function (data) {
if (data.new_rank) {
$('._user_rank').html(data.new_rank);
}
}
jQuery ajax requests provide you a way to handle request errors.
$.ajax(url, {
success: function(data) {
// success
},
error: function() {
// error
}
});
If it's not a request error that you are trying to catch you still should handle error by yourself and not let javascript throw them all the way.
One solution I would say is follow strict data type in $.ajax like dataType: json.
Use success and error handler. And if the return data is anything other than json type it will be handled through error handler.
$.ajax(url, {
dataType: 'json'
success: function(data) {
// success
},
error: function() {
// error
}
});
I have a simple Jquery ajax function call that looks like this.
function getUsers(){
var jqxhr = $.ajax({
url: "../assets/js/data/users.json",
type: "GET",
cache: true,
dataType: "json",
statusCode: {
404: handleError404("Error at getUsers();"),
500: handleError500("Error at getUsers();")
},
success: function (data) {
$.each(data, function(index, element) {
console.log(element.name);
});
}
});
}
The error handle functions look like this.
function handleError500(customMsg){
alert("Oops, there was an error: 500");
console.log("ERROR: 500 | "+customMsg);
}
function handleError404(customMsg){
alert("Oops, there was an error: 404");
console.log("ERROR: 404 | "+customMsg);
}
For some odd reason, even on a successful call with no apprent error 500 or 404 the statusCode functions are firing.
Any ideas? Thank you.
This is a common Javascript gotcha, but you're actually firing off these functions!
() after the name will actually invoke that function at that moment.
404: handleError404() // <-- it calls it immidiately
What you need to do, is create an anonymous function, which will be called later when the error actually happens, which inside of it will invoke your functions.
statusCode: {
404: function () { // <-- anonymous function won't get called until it needs to
handleError404("Error at getUsers();")
},
500: function () {
handleError500("Error at getUsers();")
}
},
Side note: If you weren't passing in parameters to your function,
you could actually ommit the anonymous function function () { /*
function call */ } part, and just call your function!
statusCode: {
404: handleErrors, // since no parameters are passed, this could be done
500: handleErrors
}
Because in the definition of the property of the object you are actually calling the function instead of defining one to be called when the code actually happens.
One thing you can try is:
statusCode: {
404: function() { handleError404("Error at getUsers();") },
500: function() { handleError500("Error at getUsers();") }
},
I'm trying to trigger a Bootstrap Growl message if $.post call fails. How I know if it fails? Because backend part (PHP script) returns this JSON:
{
"success":false,
"errors":{
"usuario":{
"captcha":[
"The captcha is not valid."
]
}
}
}
So I did this in jQuery:
$.post($form.attr('action'), $form.serialize(), function(result) {
// ... Process the result ...
}, 'json').fail(function(result) {
console.log("fail");
$.growl({
icon: "fa fa-paw",
title: "<strong>Error en datos</strong>",
message: "Los siguientes errores ocurrieron",
type: "danger",
allow_dismiss: true,
animate: {
enter: 'animated bounceInDown',
exit: 'animated bounceOutUp'
}
});
});
But none the message or the console.log() works so I don't know if the right is .fail() or .error() or all the time is success and then I need to verify if success at JSON comes with FALSE which means some error.
As a second little question, how I move inside the JSON looking for errors strings for show them as LI inside the message at Growl element?
The fail clause will be triggered if the response contains an http error, for example, a 400 or a 500. If the http response is 200 - ok, then you won't get a failure.
Try this code:
$.post($form.attr('action'), $form.serialize(), function(result) {
//code for success
if (result.success) {
//TODO
}
//code for failure
if (!result.success) {
console.log("fail");
$.growl({
icon: "fa fa-paw",
title: "<strong>Error en datos</strong>",
message: "Los siguientes errores ocurrieron",
type: "danger",
allow_dismiss: true,
animate: {
enter: 'animated bounceInDown',
exit: 'animated bounceOutUp'
}
});
}//if failed
},
'json');
I think you are confusing a captcha failure and an ajax call failure as the same thing, if I am interpreting your question correctly, that is.
The ajax call itself is succeeding, which is why you see none of the ajax failure output. The JSON returned by the SUCCESSFUL call contains information(success:"false") stating that the captcha entry failed to pass it's test. Does that make sense?
You will need to take the success attribute in the result JSON and apply appropriate actions depending on it being a success true or false.
Okay, I do use firebug to determine when a function is not defined in Dev. What I would like to do in production is show a modal window saying an error has been received which would then redirect them to another page upon click. Not so easy.
Please understand that this function does work and the component that is called works. I am going to misspell the function call on purpose to demonstrate the error I am not receiving thru the jquery ajax function.
I am using .ajaxSetup to set up the default options for several ajax functions that will be running asynch:
$.ajaxSetup({
type: "POST",
dataType: "json",
url: "DMF.cfc",
data: {
qID: 1,
returnFormat: "json"
},
beforeSend: function() {
$('#loadingmessage').fadeIn(); // show the loading message.
},
complete: function() {
$('#loadingmessage').fadeOut(); // show the loading message.
}
}); //end AjaxSetup
The actual ajax call is:
$.ajax({
data: {
method: 'getCurrentIssues'
},
success: function(response) {
nsNewDebtshowDebtIssues(response);
},//end success function
error: function(jqXHR, exception) {
alert("Error running nsNewDebt.showDebtIssues");
}
}) //end getCurrentIssues Ajax Call
The error I forced is that the method run in the success function should actually be nsNewDebt.showDebtIssues. Firebug correctly displays in console the error nsNewDebtshowDebtIssues is not defined but the actual error message for the ajax call does not run, so if an enduser was running the page it would appear the page was hung.
So, In summary I want to know how to track when such an error occurs, preferrable to place in the error section of the .ajaxSsetup but if neccessary in each .ajax call.
It is not an ajax error, so you cannot handle it from the ajaxError method.
You should do a try/catch in the success method.
success: function(response) {
try {
nsNewDebtshowDebtIssues(response);
} catch (ex) {
//exception occured
//alert("Error running nsNewDebt.showDebtIssues");
alert( ex.message + '\n\tin file : ' + ex.fileName + '\n\t at line : ' + ex.lineNumber);
}
}
Before making the call, you can do:
if(typeof nsNewDebtshowDebtIssues == 'function') {
// .. call it ..
}
Well, the error actually occurs after the AJAX call has succeeded (since it comes from your success handler), so the error handler indeed won't be called.
If you want to use the same handler for actual AJAX request errors and for further errors originating from your success handler, you can define a named function and use it both as your error handler and from a try/catch block in your success handler:
function handleError(jqXHR, status, exception)
{
alert("Error running request.");
// Or print something from 'jqXHR', 'status' and 'exception'...
}
$.ajax({
data: {
method: "getCurrentIssues"
},
success: function(response, status, jqXHR) {
try {
nsNewDebtshowDebtIssues(response);
} catch (x) {
handleError(jqXHR, status, x);
}
},
error: handleError
});