I don't understand promises/deferred very much... I have something like that :
function callAjax() {
return $.ajax({
type: 'post',
dataType: 'jsonp',
data: {status: status, name: name},
url: '/test',
xhrFields: {
withCredentials: true
}
});
}
function connectAjax() {
var msg = 'doesnt work';
var promise = callAjax();
promise.then(function(data, textStatus, xhr) {
msg = 'it worked !';
}, function(data, textStatus, xhr) {
msg = 'it failed !';
});
console.log(msg); // output 'doesnt work'
}
I have tried a lot of different things (always, done, etc..) but couldn't make it work.
I use jsonp but my request isn't cross domain. I expect a 500 error from the server for my request.
For your example to work, you have to put the 'console.log(...)' statment INSIDE the two callback functions you register on the promise with .then(.., ..).
You have to keep in mind that the promise callback functions get called, only when the ajax call is finished.
Your script however does not wait until this happens and 'console.log(msg);' is executed before the ajax call returns.
This is a great example for the non-blocking nature of JavaScript.
For more detailed understanding look up resources on the JS event loop:
https://thomashunter.name/blog/the-javascript-event-loop-presentation/
Understanding the Event Loop
Related
I'm somewhat breaking my head over this. I have an ajax call like this:
function genericname()
{
var domain = $('#input').val();
var sendData = {
'domain': domain
};
var promise = $.ajax(
{
type: 'POST',
url: '/functions.php',
data:
{
module: 'modulename',
operation: 'functionname',
parameters: sendData
},
dataType: 'json'
}).promise();
promise.then(function(data)
{
console.log(data);
return data;
});
promise.fail(function(data)
{
console.log(data);
});
}
Now the problem is that when debugging I notice that both promise.then and promise.fail are just skipped. The php proces I am calling to output is true. Actually when I look in the network tab of the debug tools the response says true.
Could anyone explain what the mistake is here?
EDIT: the result being output by the php function is json_encoded
This function is being called in the .then portion of another ajax call
remove .promise at the end of ajax request:
var domain = $('#input').val();
var sendData = {
'domain': domain
};
var promise = $.ajax(
{
type: 'POST',
url: '/functions.php',
data:
{
module: 'modulename',
operation: 'functionname',
parameters: sendData
},
dataType: 'json'
})
The issue is fixed now and here is how I solved it.
The function was required to return a boolean which was used in the if statement in another .then statement of another ajax call to change some html.
In the end I resorted to placing the html changes in the .then portion if this function.
Hope I can help someone with this information.
In typescript I have a DataAccess Class so that all Ajax calls are routed through a single object to save repetition of code in a lot of places within my application.
In using this approach I have needed to use call backs to get the response back to the calling class so that the success and error can be handled accordingly.
This is the typescript
ajaxCall(retVal, retError) {
$.ajax({
type: this.callType,
data: this.dataObject,
dataType: this.dataType,
url: this.url,
contentType: this.contentType,
traditional: this.traditional,
async: this._async,
error: retError,
success: retVal
});
}
This is the compiled Javascript
AjaxDataAccessLayer.prototype.ajaxCall = function (retVal, retError) {
$.ajax({
type: this.callType,
data: this.dataObject,
dataType: this.dataType,
url: this.url,
contentType: this.contentType,
traditional: this.traditional,
async: this._async,
error: retError,
success: retVal
});
};
return AjaxDataAccessLayer;
This calls through to the ASP.Net MVC controllers perfectly fine, however the problem that I have is regardless of Success or Error the call back is always retError.
This is the calling Typescript
var _this = this;
var dataAccess = new DataAccess.AjaxDataAccessLayer(Fe.Upsm.Enums.AjaxCallType.Post,
Fe.Upsm.Enums.AjaxDataType.json,
"../../PrintQueue/DeletePrintQueueItems",
jsonObj);
dataAccess.ajaxCall(data => {
// success
new Fe.Upsm.Head().showGlobalNotification("Selected Items Deleted");
_this.refreshPrintQueueGrid();
(window as any).parent.refreshOperatorPrintQueueCount();
}, xhr => {
// failure
alert("An Error Occurred. Failed to update Note");
});
When stepping through and looking at this the Status is OK and the response is 200.
So, Problem (as mentioned above) always calling xhr \ retError regardless of success.
Question: How do I get it to go into the right call back?
In your error handler, you were not passing all the parameters, so you are only checking whether the request finished successfully. However, there can be errors after that, like when the response is processed. You can handle errors betters like this:
dataAccess.ajaxCall(data => {
// success
new Fe.Upsm.Head().showGlobalNotification("Selected Items Deleted");
_this.refreshPrintQueueGrid();
(window as any).parent.refreshOperatorPrintQueueCount();
}, (xhr, errorText, errorThrown => {
// failure
console.log(xhr, errorTest, errorThrown);
alert("An Error Occurred. Failed to update Note");
});
Based on the discoveries using this method, the error is that your controllers are returning empty responses, so you're getting an exception when jQuery tries to parse them, because an empty string is not valid JSON.
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
}
}
});
I'm struggling with something - which I'm guessing means I've misunderstood and am doing something silly
I have an observable and need to use it to create some object, send that to the server for processing, combine a result from the server with the object I sent, and then turn that into an observable so what I want to do (I think) is something like
var theNewObservable = my.observable.things.select(function(thing) {
var dataToSend = generateMyJavascriptObjectFrom(thing);
var promise = $.ajax({
type: 'POST',
url: http://somewhere.com,
data: dataToSend
}).promise();
return rx.Observable.fromPromise(promise).subscribe(function(data, status, jqXHR) {
var infoFromServer = jqXHR.getResponseHeader('custom-header-returned');
// I'm wanting this to be the thing other code can subscribe to
return { infoFromServer: dataToSend };
}, function(err) {
alert('PC LOAD LETTER!');
console.error(err);
});
}
});
theNewObservable.subscribe(function(combinedInfo) { console.log(combinedInfo) };
where I'm expecting {infoFromServer: dataToSend} I'm getting an AutoDetachObserver and I can see that has an onNext with the ajax onSuccess signature so I'm obviously doing something silly
A couple things that should help a bit:
1) The subscribe method is a terminal method, as in, it won't return anything. It is where the Observer attaches so there should be no further data propagation after the subscribe
2) The onNext method of subscribe can only take a single value which you will need to have all the message data wrapped in.
Since jQuery's Promise will not behave well with this, you have two options. First, you can use the RX-DOM project for an Observable ajax version. Or you will need to wrap the promise method. If you further need to wait on the response you should be using selectMany instead, which will allow you to fire off the promise, then await its return and map the response to the original request.
var theNewObservable = my.observable.things
//Preprocess this so that `selectMany` will use
//dataToSend as the request object
.map(function(thing) { return generateMyJavascriptObjectFrom(thing); })
.selectMany(function(dataToSend) {
var promise = $.ajax({
type: 'POST',
url: http://somewhere.com,
data: dataToSend
}).promise();
//Rewrap this into a promise that RxJS can handle
return promise.then(function(data, status, jqXHR) {
return {data : data, status : status, jqXHR : jqXHR};
});
}, function(request, response) {
return {
infoFromServer : response.jqXHR.getResponse('custom-header'),
dataToSend : request
};
});
theNewObservable.subscribe(
function(combinedInfo) {
console.log(combinedInfo)
},
function(err) {
alert('PC LOAD LETTER!');
console.error(err);
});
I'm currently dealing with refactoring my code, and trying to automate AJAX requests as follows:
The goal is to have a context-independent function to launch AJAX requests. The data gathered is handled differently based on the context.
This is my function:
function ajaxParameter(routeName, method, array, callback){
//Ajax request on silex route
var URL = routeName;
$.ajax({
type: method,
url: URL,
beforeSend: function(){
DOM.spinner.fadeIn('fast');
},
})
.done(function(response) {
DOM.spinner.fadeOut('fast');
callback(response);
})
.fail(function(error){
var response = [];
response.status = 0;
response.message = "Request failed, error : "+error;
callback(response);
})
}
My problem essentially comes from the fact that my callback function is not defined.
I would like to call the function as such (example)
ajaxParameter(URL_base, 'POST', dataBase, function(response){
if(response.status == 1 ){
console.log('Request succeeded');
}
showMessage(response);
});
I thought of returning response to a variable and deal with it later, but if the request fails or is slow, this won't work (because response will not have been set).
That version would allow me to benefit the .done() and .fail().
EDIT : So there is no mistake, I changed my code a bit. The goal is to be able to deal with a callback function used in both .done() and .fail() context (two separate functions would also work in my case though).
As far as I can see there really is nothing wrong with your script. I've neatened it up a bit here, but it's essentially what you had before:
function ajaxParameter (url, method, data, callback) {
$.ajax({
type: method,
url: url,
data: data,
beforeSend: function(){
DOM.spinner.fadeIn('fast');
}
})
.done( function (response) {
DOM.spinner.fadeOut('fast');
if (callback)
callback(response);
})
.fail( function (error){
var response = [];
response.status = 0;
response.message = "Request failed, error : " + error;
if (callback)
callback(response);
});
}
And now let's go and test it here on JSFiddle.
As you can see (using the JSFiddle AJAX API), it works. So the issue is probably with something else in your script. Are you sure the script you've posted here is the same one you are using in your development environment?
In regards to your error; be absolutely sure that you are passing in the right arguments in the right order to your ajaxParameter function. Here's what I am passing in the fiddle:
the url endpoint (e.g http://example.com/)
the method (e.g 'post')
some data (e.g {foo:'bar'})
the callback (e.g function(response){ };)
Do you mean something like this, passing the success and fail callbacks:
function ajaxParameter(routeName, method, array, success, failure) {
//Ajax request on silex route
var URL = routeName;
$.ajax({
type: method,
url: URL,
beforeSend: function () {
DOM.spinner.fadeIn('fast');
}
}).done(function (response) {
DOM.spinner.fadeOut('fast');
success(response);
}).fail(function (error) {
var response = [];
response.status = 0;
response.message = "Request failed, error : " + error;
failure(response);
})
}
Called like:
ajaxParameter(
URL_base,
'POST',
dataBase,
function(response){
//success function
},
function(response){
// fail function
}
);