Why does jQuery.then() behave differently when using $.deferred and $.ajax - javascript

I've been trying to get my head around the jQuery Deferred object. My intention is to inspect each ajax response (success/fail). I want to do this without interfering with other code that declares a typical $.ajax().done().fail() request.
I've user the $.ajaxPrefilter() to get each ajax request before it is executed. Using the .then() method on the jqXHR object, I've managed to add a function that will be called before the .done() method placed on the original $.ajax() call
The code below will print out the following:
def done
def then
2nd ajax prefilter then
2nd ajax done
2nd ajax then
ajax done
ajax then
What I don't understand is why the prefilter step executes first. I would have expected it to have been executed last, or not at all.
The behaviour is what I want, but I don't understand why.
// this is a typical usage of deferred with two done functions added, the second via .then()
var def = $.Deferred();
def.done(function(){
document.write("def done<br>");
});
def.then(function(){
document.write("def then<br>");
});
def.resolve();
// this is a typical ajax request with a done function added, followed by another using .then()
$.ajax("/echo/json/").done(function(){
document.write("ajax done<br>");
}).then(function(){
document.write("ajax then<br>");
});
// for the third request i intercept and call the .then() method
$.ajaxPrefilter(
function( options, originalOptions, jqXHR ) {
jqXHR.then(function(data, textStatus, jqXHR){
document.write("2nd ajax prefilter then<br>");
});
});
// create a typical ajax request. these will be executed after the prefilter .then()
$.ajax("/echo/json/").done(function(){
document.write("2nd ajax done<br>");
}).then(function(){
document.write("2nd ajax then<br>");
});
Thanks in advance for any help
UPDATE: ------------
From #Bergi response, the code below demonstrates how the $.ajaxPrefilter() is called before the done().
$.ajaxPrefilter(
function( options, originalOptions, jqXHR ) {
document.write("prefilter function within $.ajax call<br>");
jqXHR.then(function(data, textStatus, jqXHR){
document.write("2nd ajax prefilter then<br>");
});
});
var functionToRunWhenDoneIsCalled = function() {
document.write("done is called function<br>");
return function(){
document.write("2nd ajax done<br>");
}
}
$.ajax("/echo/json/").done(
(functionToRunWhenDoneIsCalled)()
).then(function(){
document.write("2nd ajax then<br>");
});
This outputs:
prefilter function within $.ajax call
done is called function
2nd ajax prefilter then
2nd ajax done
2nd ajax then
Which answers my question about how the .then() method is attached to the deferred jqXHR object before the .done() method.

In your case, there is no difference between adding callbacks with .done() or with .then(). Using only .done() would be enough.
What I don't understand is why the prefilter step executes first. I would have expected it to have been executed last, or not at all.
Callbacks are executed in the order they are added to the deferred object. And the prefilter is executed inside of $.ajax, i.e. the callback is attached even before the $.ajax call returns and your done and then handlers can be attached.

All .then does if you don't return a deferred object is add another done fail and/or progress handler to the deferred object. with that in mind, it makes complete sense for the .then added in the pre-filter to execute before the one added after $.ajax() because the code in the pre-filter callback happened first. The callbacks get triggered first in first out.

What I don't understand is why the prefilter step executes first. I would have expected it to have been executed last, or not at all.
You've attached another "thing to do" to the jqXHR that is associated with the ajax request. Since it's a pre-filter, that gets attached before the standard done/fail that the ajax request uses. Handlers run in the order they were attached and the prefilter one is therefore first.
Note that since the prefilter only attached a single function in the .then() method, nothing will run if the request fails for some reason. Sounds like you'd want to have the second (failure handler) arg as well.
As for the completion order of the two different ajax requests, that is not predictable. It will just depend on which one returns first.

Related

How to wait for a JSONModel.loadData() request in UI5

In SAPUI5/OpenUI5, I have a JSONModel I populate by a file from server:
var oModel = new JSONModel();
oModel.loadData("http://127.0.0.1/data/config.json");
console.log(JSON.stringify(oModel.getData()));
The console logs undefined since the request is asynchronous.
How to make it synchronous so console.log() is called after the data was loaded?
Using synchronous ajax requests is not recommended as it blocks the UI and will probably result in a warning in the console.
You can attach to the Model.requestCompleted event to access the asynchronously loaded data:
oModel.attachRequestCompleted(function() {
console.log(oModel.getData());
});
The keyword you are looking for is "Deferred"-object --> it enables you to wait for an AJAX request in SAPUI5.
Check this for SAPUI5 context: SAPUI5 Wait for an Deferred-Object // wait for .done() function
Since UI5 version 1.64.0, the API loadData returns a Promise instance:
logLoadedData: async function () {
const jsonModel = new JSONModel();
await jsonModel.loadData("<host>/data/config.json");
console.log(jsonModel.getData()); // after the loadData promise is resolved
},
Alternatively, there is also the API dataLoaded which returns a promise as well. It will resolve when all requests sent by loadData are finished. Here is a syntax without async-await:
doSomethingWith: async function (jsonModel) {
// Not sure if the model has all data loaded? Just use dataLoaded:
await jsonModel.dataLoaded();
console.log(jsonModel.getData());
},
The API loadData is also called internally when the constructor function of JSONModel was called with a string (URL) as an argument. In that case, dataLoaded might come in handy as well.
You can use the attachRequestCompleted-listener from the Model [1]
model.attachRequestCompleted(function(){
console.log(this.getData()); //"this" is the model
});
Another function to use is
$.get(url, function(response){
console.log(response);
model.setData(response);
});
// or
$.ajax(url, {
success: function(){
console.log(response);
model.setData(response);
}
});
This has the advantage that you can configure the request with every setting that jQuery.ajax accepts [2]
Another way to achieve this is to use the attachEventOnce method from EventProvider.
oModel.attachEventOnce("requestCompleted", function(oEvent) {
console.log(JSON.parse(oEvent.getParameter("response").responseText));
}, this);
It's best to use this approach when you only need to react to one request, and not all. Otherwise, if you use oModel.attachRequestCompleted(...), all requests will go through the same handler function.
You can also use method chaining to make this a little easier.
oModel.attachEventOnce(...) returns the object that called the method, so you can load your data and handle the callback all in one statement.
oModel.attachEventOnce("requestCompleted", function(oEvent) {
console.log(JSON.parse(oEvent.getParameter("response").responseText));
}, this).loadData("http://127.0.0.1/data/config.json");
This will first execute the loadData() request, and then console the response when the request has been completed. It will only use the callback function the first time a request is made. Subsequent requests will not go through the callback function.
If you want ALL requests to go through the SAME callback function, you can do the same thing but using oModel.attachRequestCompleted(...)
oModel.attachRequestCompleted(function(oEvent) {
console.log(JSON.parse(oEvent.getParameter("response").responseText));
}, this).loadData("http://127.0.0.1/data/config.json");
This will execute the loadData() request, console the response, and also console the response of all subsequent requests.
NOTE: Be careful using this in the callback functions. If you don't pass this as a parameter of the attachRequestCompleted(...) or attachEventOnce(...) methods, then this will lose it's original context as the controller, and inherit the context of the object calling the function. herrlock's answer demonstrates how the context of this changes.
Event Provider API Reference
Turned out there is a parameter in the .loadData() function to create a sync- call:
oModel.loadData("http://127.0.0.1/data/config.json", "", false);
See API-Reference as well.

Wait until all jQuery ajax calls are completed (done or rejected) [duplicate]

This question already has answers here:
$.Deferred: How to detect when every promise has been executed
(3 answers)
Closed 7 years ago.
I've looked through many similar questions but the proposed solutions do not work in all cases as expected. The following code works fine when all ajax calls are successfully done but if any of ajax calls happens to fail, then onComplete is immediately called:
var deferredArray = $('.preload').map(function() {
return $.get(this.href)
});
$.when.apply($, deferredArray).then(onComplete, onComplete);
So there can be two cases:
all deferred calls are successful, then onComplete is called afterwards - works fine;
some deferred call fails (returns HTTP 400 Bad request), then onComplete is called immediately not waiting for other deferred calls.
The second case represents my problem. It should always wait for all calls to complete regardless of the status before calling onComplete.
I use jQuery 1.7.1 as it's built into the framework. If the issue is due to the version, I can upgrade but I'd prefer to keep the current version.
You could try ajaxStop(). Allbeit not the most elegant solution.
$( document ).ajaxStop(function() {
// do some stuff now that all the ajax stuff has stopped
})
Also note:
If $.ajax() or $.ajaxSetup() is called with the global option set to false, the .ajaxStop() method will not fire.
I'm not sure if this outside of the scope of the types of solutions you're looking for, but I've recently been taking advantage of bluebird.js to wrap all my jquery ajax calls.
For example:
Promise.resolve($.get("http://www.google.com")).then(function() {});
What this allows me to do is then perform a Promise.all which will then wait for all the ajax calls to be complete. Each individual promise can pass or fail, but .then (or .spread) on the .all will only be called if they all succeed.
var aPass = function(){
return Promise.resolve($.get("/echo/json/"))
};
var aFail = function(){
return Promise.resolve($.get("/echo/jsonFake/")) // this will fail
};
var allPass = [aPass(), aPass(), aPass()];
var oneFails = [aPass(), aPass(), aFail()];
Promise.all(allPass).spread(function(a,b,c){
console.log("All Done!");
}).catch(function(){
console.log("NEVER FAILS!");
});;
Promise.all(oneFails).spread(function(a,b,c){
console.log("never goes in here");
}).catch(function(){
console.log("FAILED");
});
Here is a jsfiddle demonstrating this: http://jsfiddle.net/ys598t4s/2/

Ajax response handling and assigning to variable

Hello i have some problem with ajax response handling
i have a global function call() that makes ajax calls and just returns json response:
function call(request_url,params) {
$.post(request_url,params,function(response) {
return response;
},'json');
}
After that i have an object GetServices that uses global function call()
var GetServices = {
service_url:"http://xxx.com/req.php",
getCurrency:function() {
var resp = call(this.service_url,{act:'getCurrency'});
return resp;
}
}
I want GetServices.getCurrency() to return the ajax response but it returns undefined. Javascript assigns value undefined to it and after that finishes ajax call.
Please help me how to fix this.
This pattern will not work because the call is still executing while the resp variable is being set, therefore resp will always be null.
The alternative is to pass a delegate function to the call function to run on $.post success, or change the $.post to be a synchronous $.ajax call.
Its an asynchronous call...
Therefore it will make a call and resume its processing. You will get the value somewhere after making the call. It depends on various factors. For eg, your server's response time.
The easiest way is to call the target function (the function to which you want to pass this ajax value to) from your success handler of $.post()
For eg
function call(request_url,params) {
$.post(request_url,params,function(response) {
yourTargetFunction(response); // instead of returning the value from this
},'json');
}

Meaning of function(text) when call Ajax in Javascript

I'm totally new to javascript and have difficulty in understanding the meaning of the scripts. :( Hope someone can help me or give some advice, thaks! :)
I have a javascript in which it call Ajax function like:
callAjax('../team.cgi', 'POST', data, function(text)
In the ajax2.js it define the function callAjax like:
function callAjax(url, method, data, handler, waittime, timeout_func)
My question is what's the parameter function(text)?
function(text) is the beginning of the definition of an anonymous function. Presumably the call looks something like this:
callAjax('../team.cgi', 'POST', data, function(text)
{
// do something with text
});
The function defined between those brackets takes a variable, text, and does something with it. This is possible in JavaScript because functions are first-class citizens. They can be assigned to variables, defined anonymously, etc.
Typically, you'd say the handler parameter of the callAjax function is a callback. It's a function that will be passed certain arguments when the Ajax call completes. This is typical of asynchronous code.
The snippet above is functionally the same as this:
function doSomethingWhenAjaxCompletes(text) {
// do something
}
callAjax('../team.cgi', 'POST', data, doSomethingWhenAjaxCompletes);
The only difference in the first is that the function isn't defined with the name doSomethingWhenAjaxCompletes; it's defined anonymously instead.
Without seeing the context of the handler function my guess would be that this is the returned value from the ajax call. For example, if team.cgi in your example above returns an xml list of teams then I would expect the text parameter in the handler function to be that list.
When issuing AJAX requests, execution does not halt and wait for the response to come back. Instead, the request is send and execution continues. You simply provide what is called a "callback" function which is called when the AJAX response is returned. Typically, the callback function takes a single argument containing the response object or message that was returned as the answer to your AJAX request.
callAjax('../team.cgi', 'POST', data, function(text) {
console.log('Got a response!');
console.log(text);
}
This simply sends the request. At some time later (after a pause due to network latency) you will see the console log message appear, indicating that a response was received and the callback function meant for "handling" the response has been called.

Continue function after ajax request has been completed

I was wondering if it was possible to have a function call a function that has an ajax request, and continue executing when the ajax request finished. Here is example pseudo code.
function func1(){
//do things
func2();
//**How would I get this code to execute after the ajax request is finished.
}
function func2(){
$.ajax({
url: "test.html",
context: document.body,
success: function(){
//do some things
}
});
I do not want to execute everything in a callback function, as this function is called many times in a loop.
Thanks!
You have two choices.
Use a callback function for the processing that happens after the AJAX call.
Use a synchronous "JAX" call ... remember, the A in AJAX is Asynchronous.
Otherwise, there's not a good way to accomplish what you describe.

Categories

Resources