How can I make second AJAX request in the function below asynchronous instead of synchronous? result is a string that should start with 'start of string' and end with 'end of string' but in the middle of the string will be the results of an initial AJAX request that is being iterated.
Foo = {
foo: function() {
$(document).on("change", '.foo', function(e) {
e.preventDefault();
$.ajax({
url: "foo.php",
success: function(rows) {
$.each(rows, function() {
var result = 'start of string'; // START
$.ajax({
url: "bar",
async: false, // I DON'T want this
success: function(data) {
result += data; // MIDDLE
}
});
result += 'end of string'; // END
});
}
});
});
}
}
Thank you.
You can take advantage of jquery deferred objects. You can have two different ajax calls in different functions and then you can use .done() method to make sure you get the final string once both the ajax calls have been completed.
Read more here:
http://api.jquery.com/category/deferred-object/
http://api.jquery.com/deferred.done/
In jquery the way to chain asynchronous calls which are executed one after other, is using promise.then(), which was also called promise.pipe() in previous jquery versions.
Foo = {
foo: function() {
$(document).on("change", '.foo', function(e) {
e.preventDefault();
var param1 = {url: "foo.php"};
var param1 = {url: "bar"};
$.ajax(param1)
.then(function(rows) {
var result = 'start of string'; //START
var fn = function(data) {
result += data;
}
var last, first;
$.each(rows, function() { // rows should be iterable
if (!last) {
first = last = $.ajax(param2).done(fn)
} else {
last = last.then(function(res) {
return $.ajax(param2).done(fn);
});
}
});
last.done(fn).done(function() {
result += 'end of string'; // END
});
return first;
});
});
}
}
The easiest way to do this is indeed by using deferred objects, the example of your JavaScript using the jQuery when and done deferred statements;
$.when(ajaxCallOne()).done(ajaxCallTwo(rows));
function ajaxCallOne() {
return $.ajax({
url : "foo.php",
async: true
});
}
function ajaxCallTwo(rows) {
var result = 'start of string';
$.each(rows, function() {
$.ajax({
url : "bar",
async : true,
success : function(data) {
result += data; // MIDDLE
}
});
result += 'end of string'; // END
});
return result;
}
I'm not a JavaScript nor jQuery expert, but I think you should look at those deferred objects.
http://api.jquery.com/category/deferred-object/
http://api.jquery.com/jQuery.when/
I think you should look at the javascript promise design pattern
let me explain little to you:
This promise is a sort of proxy, representing the future result of the
operation. You would then register a callback on the promise, which
will be executed by the promise once the operation does complete and
the result is available.
Using jQuery you can define promise like this:
var promise = $.getJSON('url');
then using your promise variable you can define what to do when your request is done or failed or even do some function in case it failed or succeded.
promise.done(function(s) {alert('done successfully'); });
promise.fail(function(){ alert('get failed!'); });
promise.always(function(){ alert('this 'll executed anyway!'); });
there are plenty of tutorials on it
Promises and Deferred objects in jQuery
jQuery: Deferred Object
javascript promises
What is the benefit of a 'promise' abstraction in CommonJS?
Related
I am making a chain of AJAX calls like in the example below, which I found at http://www.dotnetcurry.com/jquery/1022/jquery-ajax-deferred-promises.
How can I adjust the code to prevent that function B gets called if the response from the AJAX call in function A is an empty JSON object array (i.e. "[ ]")? Ideally I would like to not only abort the chain of AJAX calls, but also inform the user that no result was found.
Thanks!
function A() {
writeMessage("Calling Function A");
return $.ajax({
url: "/scripts/S9/1.json",
type: "GET",
dataType: "json"
});
}
function B(resultFromA) {
writeMessage("In Function B. Result From A = " + resultFromA.data);
return $.ajax({
url: "/scripts/S9/2.json",
type: "GET",
dataType: "json"
});
}
function C(resultFromB) {
writeMessage("In Function C. Result From B = " + resultFromB.data);
return $.ajax({
url: "/scripts/S9/3.json",
type: "GET",
dataType: "json"
});
}
function D(resultFromC) {
writeMessage("In Function D. Result From C = " + resultFromC.data);
}
A().then(B).then(C).then(D);
function writeMessage(msg) {
$("#para").append(msg + "<br>");
}
Generically, you could make every one of your worker functions A, B, C, D return an object that contains the response data and whether you want to continue calling the next function in the chain, whatever that may be. Something like this:
function A(input) {
return $.get("/scripts/S9/1.json").then(function (response) {
return {
continue: response.data && response.data.length,
data: response
};
});
}
Now you can make a promise chain by reducing an array of worker functions, and deciding in every step whether you want to continue (in which case you execute the next worker) or not (in which you simply keep returning the last valid result for the rest of the chain). I've wrapped this logic into a function breakableChain, for lack of a better name.
function maybe() { return Math.random() > 0.25; }
function A(input) { console.log('in A:', input.data); return {continue: maybe(), data: 'result from A'}; }
function B(input) { console.log('in B:', input.data); return {continue: maybe(), data: 'result from B'}; }
function C(input) { console.log('in C:', input.data); return {continue: maybe(), data: 'result from C'}; }
function D(input) { console.log('in D:', input.data); return {continue: maybe(), data: 'result from D'}; }
function breakableChain(workers, init) {
return workers.reduce(function (current, next) {
return current.then(function (result) {
return result.continue ? next(result) : result;
});
}, $.Deferred().resolve({continue: true, data: init}));
}
breakableChain([A, B, C, D], 'initial data').then(function (result) {
console.log('overall:', result.data);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Of course you can make a non-generic version of this that doesn't need a continue flag, but instead hard-codes the assumptions about each result directly into the if statement inside .reduce(). That would be shorter, but not re-usable.
The whole {continue: true, data: result} thing is just a convention. Your convention could also be "if the previous call returned anything, then continue, otherwise stop", the approach would be the same, but it would become impossible to return an overall result.
I have a function with Ajax call. If it returns true, another function with another Ajax should be run. But in my case first function returns true but the second function doesn't run. How to do that?
If I remove this: if(checkIfCompleted()) { from the second function, it's working. But I have to check if first function returns true.
My code is:
function checkIfCompleted() {
$.ajax(
{
url: Base_URL + "/offers/" + 'checkIfQuoteIsComplete',
type: "POST",
data: {
"offer_id": offer_id
},
success: function (data) {
if(data === 'notcompleted') {
$("#NotCompleteQuotes").modal();
return false;
} else {
return true;
}
},
error: function (data) {
MessageBoxError('Error');
}
});
}
Second function that should run after first function returns true is:
function saveOrEditQuote(status, id) {
if(checkIfCompleted()) {
//my code here - it's long
return false;
}
}
Javascript is asynchronous, try using callbacks like this
function checkIfCompleted(callback) {
$.ajax(
{
url: Base_URL + "/offers/" + 'checkIfQuoteIsComplete',
type: "POST",
data: {
"offer_id": offer_id
},
success: function (data) {
if(data === 'notcompleted') {
$("#NotCompleteQuotes").modal();
} else {
callback();
}
},
error: function (data) {
MessageBoxError('Error');
}
});
}
then
function saveOrEditQuote(status, id) {
checkIfCompleted(function(){
// will be done only on success
//my code here - it's long
return false;
})
}
Explanation:
A callback is simply another argument, the difference is that it is a function, not for example an integer. By using them you can make sure some block of code is called only after something. They're really helpful for actions that take some time (like requests, jquery animations, timeouts).
All you have to do is pass in the second callback function saveOrEditQuote() into the body of the success callback. You'll need be careful to declare that function in the right scope, because the success callback will be a different scope than checkIfCompleted.
Also, often the success argument data will be an array of objects and strings so data === 'notcompleted' may be comparing an array to a string.
function checkIfCompleted (){
var success = saveOrEditQuote;
$.ajax({ ... }).success(function(data){ success(true); })
}
function saveOrEditQuote(isCompleted){...};
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
If I call jQuery.ajax() inside a loop, would it cause the call in current iteration overwrite the last call or a new XHR object is assigned for the new request?
I have a loop that do this, while from console log I can see requests done 200 ok but just the result data of the last request in the loop is stored by the request success callback as supposed .
the code:
var Ajax = {
pages: {},
current_request: null,
prefetch: function () {
currentPath = location.pathname.substr(1);
if(this.pages[currentPath])
{
var current = this.pages[currentPath];
delete this.pages[currentPath];
current['name']=currentPath;
current['title']=$("title").text().replace(' - '.SITE_NAME, '');
current['meta_description']=$("meta[name=description]").attr('content');
current['meta_keywords']=$("meta[name=keywords]").attr('content');
}
var _Ajax = this;
//the loop in question *****
for(var key in this.pages)
{
$.ajax({
method: 'get',
url:'http://'+location.hostname+'/'+key,
success: function(data) {
_Ajax.pages[key] = data;
}
});
console.debug(this.pages);
}
if(current)
{
this.pages[currentPath] = current;
}
}
};//Ajax Obj
for(var i in pages)
{
Ajax.pages[pages[i]]={};
}
$(function() {
Ajax.prefetch();
});//doc ready
You'll need a closure for key:
for(var k in this.pages){
(function(key){
$.ajax({
method: 'get',
url:'http://'+location.hostname+'/'+key,
success: function(data) {
_Ajax.pages[key] = data;
}
});
console.debug(this.pages);
})(k);
}
that way you make sure that key is always the correct on in each ajax success callback.
but other than that it should work
i made a small closure demonstration using timeout instead of ajax but the principle is the same:
http://jsfiddle.net/KS6q5/
You need to use async:false in you ajax request. It will send the ajax request synchronously waiting for the previous request to finish and then sending the next request.
$.ajax({
type: 'POST',
url: 'http://stackoverflow.com',
data: data,
async: false,
success: function(data) {
//do something
},
error: function(jqXHR) {
//do something
}
});
I believe what's happening here has to do with closure. In this loop:
for(var key in this.pages)
{
$.ajax({
method: 'get',
url:'http://'+location.hostname+'/'+key,
success: function(data) {
_Ajax.pages[key] = data;
}
});
console.debug(this.pages);
}
The variable key is actually defined outside the for loop. So by the time you get to the callbacks, the value has probably changed. Try something like this instead:
http://jsfiddle.net/VHWvs/
var pages = ["a", "b", "c"];
for (var key in pages) {
console.log('before: ' + key);
(function (thisKey) {
setTimeout(function () {
console.log('after: ' + thisKey);
}, 1000);
})(key);
}
I was facing the same situation, I solved using the ajax call inside a new function then invoke the function into the loop.
It would looks like:
function a(){
for(var key in this.pages)
{
var paramsOut [] = ...
myAjaxCall(key,paramsOut);
.......
}
}
function myAjaxCall(paramsIn,paramsOut)
{
$.ajax({
method: 'get',
url:'http://'+location.hostname+'/'+paramsIn[0],
success: function(data) {
paramsOut[key] = data;
}
});
}
This is how I always do a ajax loop..
I use a recursive function that gets called after the xhr.readyState == 4
i = 0
process()
function process() {
if (i < 10) {
url = "http://some.." + i
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
alert(xhr.responseText)
i++
process()
}
}
xhr.send();
} else {
alert("done")
}
}
The variable ajaxdata is modified within the success function, if that hasn't been done yet, I would like to wait 2 seconds, then continue without it.
The use case is for a jqueryui autocomplete field. The autocomplete source is an ajax request, but if the user types quickly, and exits the field before the list loads, the field remains unset. Using the 'change' event on the autocomplete I check if the user entered a valid option without selecting it, but this doesn't work if the source hasn't loaded when the change event fires. So I would like to put a delay in the change function which waits, if the source (stored in the variable 'ajaxdata') is empty.
code:
input.autocomplete({
source: function (request, response){
$.ajax(
{
type: "GET",
url: "/some/url",
dataType: "json",
success: function(data){
response($.map(data,function(item){
return{
label: item.label,
value: item.value
}
}));
ajaxdata = data;
}
}
);
// ajaxopts = ajaxsource(request,response,ajaxurl,xtraqry)
},
change: function(event, ui) {
if (!ui.item) {
// user didn't select an option, but what they typed may still match
var enteredString = $(this).val();
var stringMatch = false;
if (ajaxdata.length==0){
/// THIS IS WHERE I NEED A 2 SECOND DELAY
}
var opts = ajaxdata;
for (var i=0; i < opts.length; i++){
if(opts[i].label.toLowerCase() == enteredString.toLowerCase()){
$(this).val(opts[i].label);// corrects any incorrect case
stringMatch = true;
break;
}
}
}
},
});
Edit:
To be more specific about the problem: This delay needs to be conditional. Meaning that if the data is already loaded (either because it came from a static source, or from an earlier ajax call) I do not want to have a delay.
If I'm understanding you properly, I think you just want to check and see if ajaxdata has been populated; but if it hasn't, only wait two more seconds and then just proceed without it.
Try this:
change: function(event, ui) {
if (!ui.item) {
// user didn't select an option, but what they typed may still match
if (ajaxdata.length==0){
/// THIS IS WHERE I NEED A 2 SECOND DELAY
//pass in 'this' so that you can use it
setTimeout(function() {correctCase(this);}, 2000);
}
}
}
. . . . .
function correctCase(inThis){
//I'm not sure what this variable does. do you really need it???
var stringMatch = false;
var enteredString = $(inThis).val();
//you still want to be sure that ajaxdata is not empty here
if (ajaxdata.length==0){
var opts = ajaxdata;
for (var i=0; i < opts.length; i++){
if(opts[i].label.toLowerCase() == enteredString.toLowerCase()){
$(inThis).val(opts[i].label); // corrects any incorrect case
stringMatch = true; //this variable doesn't seem to do anything after this???
break;
}
}
}
}
I'm not really sure what it is you're trying to do, but I'm pretty sure something like this would be a better way of doing it :
input.autocomplete({
source: function(request, response) {
return $.ajax({
type: "GET",
url: "/some/url",
dataType: "json"
});
},
change: function(event, ui) {
if (!ui.item) {
// user didn't select an option, but what they typed may still match
var enteredString = this.value;
var stringMatch = false;
//make sure ajax is complete
this.source().done(function(data) {
var opts = $.map(data, function(item) {
return {
label: item.label,
value: item.value
}
});
for (var i = 0; i < opts.length; i++) {
if (opts[i].label.toLowerCase() == enteredString.toLowerCase()) {
$(this).val(opts[i].label); // corrects any incorrect case
stringMatch = true;
}
}
});
}
}
});
By default, JavaScript is asynchronous whenever it encounters an async function, it queued that function for later.
But if you want a pause js(ajax call or anything) for you can do it use promises
Case 1: output hello(will not wait for setTimeout)
https://jsfiddle.net/shashankgpt270/h0vr53qy/
//async
function myFunction() {
let result1='hello'
//promise =new Promise((resolve,reject)=>{
setTimeout(function(){
resolve("done");
result1="done1";
}, 3000);
//});
//result = await promise
alert(result1);
}
myFunction();
case 2: output done1(will wait for setTimeout)
https://jsfiddle.net/shashankgpt270/1o79fudt/
async function myFunction() {
let result1='hello'
promise =new Promise((resolve,reject)=>{
setTimeout(function(){
resolve("done");
result1="done1";
}, 3000);
});
result = await promise
alert(result1);
}
myFunction();
Right now I have a collection that fetches value, and after that every view attached to the reset event get rendered again
the problem is that I also have to issue another query to fetch the total number of records retrieved, and only after that ajax call is completed the reset event should be triggered
is more clear with a bit of code:
fetch: function() {
options = { data: this.getParams() };
this.fetch_total();
return Backbone.Collection.prototype.fetch.call(this, options);
},
fetch_total: function() {
var that = this;
var options = {
url: this.url + '/count',
data: this.getParams(),
contentType: 'application/json',
success: function(resp, status, xhr) {
that.total = parseInt(resp);
return true;
}
};
return $.ajax(options);
}
as you can see, I have to issue a get to localhost/myentity/count to get the count of entities...
The thing is I need the collection.total varaible to be updated before refreshing the views, that means I need both request, the GET to localhost/myentity and to localhost/myentity/count, to be completed before refreshing all the views...
any idea how can I achieve it???
If your $ of choice is jQuery>1.5, you could take advantage of the deferred object to manually trigger a reset event when both calls have completed. Similar to your answer, but a bit more readable and without chaining the calls:
fetch: function() {
options = {silent: true, data: this.getParams()};
var _this = this;
var dfd_total = this.fetch_total();
var dfd_fetch = Backbone.Collection.prototype.fetch.call(this, options);
return $.when(dfd_total, dfd_fetch).then(function() {
_this.trigger('reset', _this);
})
},
fetch_total: function() {
// what you have in your question
}
And a Fiddle simulating these calls http://jsfiddle.net/rkzLn/
Of course, returning the results and the total in one fetch may be more efficient, but I guess that's not an option.
I think #nikoshr's answer is a good one so that you don't have to modify your API. If you think that you want to lessen your calls to the server, then consider returning an object from that endpoint that has paging information.
{
count: 1243,
page: 3,
per_page: 10,
results: [
...
]
}
and then overriding the collection's parse functionality
parse: function(res) {
this.count = res.count;
this.page = res.page;
this.per_page = res.per_page;
// return the collection
return res.results;
}
RESOURCES
http://backbonejs.org/#Collection-parse
I think I found a way to do it. What I did was to silently fire the fetch call, without triggering the 'reset' event
There, from the callback, I issue the fetch of the total (GET to localhost/myentity/count)
and from the total callback, I finally trigge the reset event
in code is something like this:
fetch: function() {
var that = this;
options = {
// will manually trigger reset event after fetching the total
silent: true,
data: this.getParams(),
success: function(collection, resp) {
that.fetch_total();
}
};
return Backbone.Collection.prototype.fetch.call(this, options);
},
fetch_total: function() {
var that = this;
var options = {
url: this.url + '/count',
data: this.getParams(),
contentType: 'application/json',
success: function(resp, status, xhr) {
that.total = parseInt(resp);
// manually trigger reset after fetching total
that.trigger('reset', that);
return true;
}
};
return $.ajax(options);
}
This is my first attempt, I wonder if there's an easier way