If I make an ajax call, I can add success handling. I want to add similar logic to my custom functions.
I have 6-10 custom functions that MUST be run sequentially or independently. They don't typically run independently so I have them daisy-chained now by calling the next function at the end of the previous but that is messy to read and does not allow for separate execution.
I would love to have something like this:
function runall(){
runfirst().success(
runsecond().success(
runthird()
))
}
I have had other situations were I would like to add .success() handling to a custom function, but this situation made it more important.
If there is another way to force 6-10 functions to run synchronously, that could solve this problem, but I would also like to know how to add success handling to my custom functions.
I tried the following based on #lanzz's suggestion:
I added .then() to my function(s):
$bomImport.updateGridRow(rowId).then(function () {
$bomImport.toggleSubGrid(rowId, false);
});
var $bomImport = {
updateGridRow: function (rowId) {
$('#' + rowId + ' td[aria-describedby="bomImport_rev"]').html($("#mxRevTxt").val());
$('#' + rowId + ' td[aria-describedby="bomImport_itemno"]').html($("#itemNoTxt").val());
$('#' + rowId + ' td[aria-describedby="bomImport_used"]').html($("#usedTxt").val());
$('#' + rowId + ' td[aria-describedby="bomImport_partSource"]').html($("#partSourceTxt").val());
$('#' + rowId + ' td[aria-describedby="bomImport_partClass"]').html($("#partClassTxt").val());
$('#' + rowId + ' td[aria-describedby="bomImport_partType"]').html($("#partTypeTxt").val());
$('#' + rowId + ' td[aria-describedby="bomImport_partno"]').html($("#mxPnTxt").val());
$('#' + rowId + ' td[aria-describedby="bomImport_descript"]').html($("#descTxt").val());
$('#' + rowId + ' td[aria-describedby="bomImport_qty"]').html($("#qtyTxt").val());
$('#' + rowId + ' td[aria-describedby="bomImport_custPartNo"]').html($("#custPartNoTxt").val());
$('#' + rowId + ' td[aria-describedby="bomImport_crev"]').html($("#custRevTxt").val());
$('#' + rowId + ' td[aria-describedby="bomImport_u_of_m"]').html($("#uomTxt").val());
$('#' + rowId + ' td[aria-describedby="bomImport_warehouse"]').html($("#warehouseTxt").val());
$('#' + rowId + ' td[aria-describedby="bomImport_standardCost"]').html($("#stdCostTxt").val());
$('#' + rowId + ' td[aria-describedby="bomImport_workCenter"]').html($("#wcTxt").val());
var defferred = new $.Deferred();
return defferred.promise();
}};
The code correctly goes to the end of updateGridRow, gives no errors, but never gets back to call the second function.
I also tried the following as was suggested #Anand:
workSheetSaveExit(rowId, isNew).save().updateRow().toggle();
function workSheetSaveExit(){
this.queue = new Queue;
var self = this;
self.queue.flush(this);
}
workSheetSaveExit.prototype = {
save: function () {
this.queue.add(function (self) {
$bomImport.workSheetSave(rowId, isNew);
});
return this;
},
updateRow: function () {
this.queue.add(function (self) {
$bomImport.updateGridRow(rowId);
});
return this;
},
toggle: function () {
this.queue.add(function (self) {
$bomImport.toggleSubGrid(rowId, false);
});
return this;
}
};
Which didn't work.
Final Solution
For a great explanation of how to use deferred and make this work see here:
Using Deferred in jQuery
How to use Deferreds:
function somethingAsynchronous() {
var deferred = new $.Deferred();
// now, delay the resolution of the deferred:
setTimeout(function() {
deferred.resolve('foobar');
}, 2000);
return deferred.promise();
}
somethingAsynchronous().then(function(result) {
// result is "foobar", as provided by deferred.resolve() in somethingAsynchronous()
alert('after somethingAsynchronous(): ' + result);
});
// or, you can also use $.when() to wait on multiple deferreds:
$.when(somethingAsynchronous(), $.ajax({ something })).then(function() {
alert('after BOTH somethingAsynchronous() and $.ajax()');
});
If your functions simply make an AJAX request, you can just return the actual promise returned by $.ajax():
function doAjax() {
return $.ajax({ /* ajax options */ });
}
doAjax().then(function() {
alert('after doAjax()');
});
From what I can tell you really just want a better way to organize these callbacks. You should use a FIFO array or a queue. Your run all should do your stacking for you then execute the first function.
var RunQueue = function(queue){
this.init(queue);
}
var p = RunQueue.prototype = {};
p.queue = null;
p.init = function(queue){
this.queue = queue.slice(); //copy the array we will be changing it
// if this is not practical, keep an index
}
p.run = function(){
if(this.queue && this.queue.length) {
var first = this.queue[0];
this.queue.shift();
var runQueue = this;
first(function(){ /*success callback parameter*/
runQueue.run();
});
}
}
Usage:
var queue = [runFirst, runSecond, runThird, ...]
(new RunQueue(queue)).run();
If you really want to get fancy, and you may need to, you could pass in Objects in the array containing your parameters and have RunQueue append the last parameter as the success callback. You could even pass in the context to run the function in that object then call apply or call (whichever one uses the array) on your method.
{
method: runFirst,
context: someObject,
parameters: [param1, param2, param3];
}
If Each of your function returns a state/function and then probably you could add a prototype to each state/function, then you would be able to call the functions like this, in fluent api way(method chaining).
runfirst().runSecond().runThird()
and so on.
Lemme try to build a sample.
EDIT
See this, if it fits your design
EDIT 2
I did not realise, you were talking about async method chaining.
There is very good example here. It was discussed in this stackoverflow thread
Related
I want to control the number of ajax calls to a controller using a while loop.
var counter = 0;
$('#filter-form').submit(function (event) {
event.preventDefault();
alert("counter init = " + counter)
while (counter < 10) {
(function () {
$.ajax({
url: '/algorithm',
method: 'GET',
data: $('#filter-form').serialize() + "&counter=" + counter,
success: function (data) {
alert("The data is " + data);
setCounter(parseInt(data))
},
error: function (xhr, status, error) {
var err = eval("(" + xhr.responseText + ")");
alert(err.Message);
}
});
})();
}
alert("counter end = " + counter)
});
function setCounter(data) {
counter = data
}
Controller:
#RequestMapping(value = "/algorithm")
#ResponseBody
public String test(#RequestParam Map<String, String> allRequestParam) {
int counter = Integer.parseInt(allRequestParam.get("counter"));
counter++;
return Integer.toString(counter);
}
The controller basically just increments the counter and returns it and in the ajax success: it will set the global counter to that number.
When I do this, the page just freezes and I cannot click anything. I put the ajax call in a function for scoping but it still does not work. When I use a for loop, it seems the ajax does not invoke because I do not get any success or error alerts.
It doesn't work for a simple reason: the $.ajax call is asynchronous.
Take this example:
$(function() {
var t = 1;
console.log("Hey, the ajax will start! t's value: " + t);
$.ajax({
url: 'www.google.com.br',
method: 'GET',
success: function (data) {
t++;
console.log("We've received an answer! t's (incremented) value: " + t);
},
error: function (xhr, status, error) {
t++;
console.log("We've received an error! t's (incremented) value: " + t);
}
});
console.log("Hey, the ajax just ended.... Not really. t's value: " + t);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
The output is:
Hey, the ajax will start! t's value: 1
Hey, the ajax just ended.... Not really. t's value: 1
We've received an error! t's (incremented) value: 2
That's because the $.ajax call is nonblocking, thus is doesn't block the program until it is finished, allowing the program to keep on executing the next line of code and continue running the ajax task in the background.
It is a recurrent issue in SO, so instead of providing solutions again here I'll ask you to read more on the questions:
How do I return the response from an asynchronous call?
How can I get jQuery to perform a synchronous, rather than asynchronous, Ajax request?
What does Asynchronous means in Ajax?
while will block synchronously until its condition is reached. Even if responses come back, the response will be asynchronous; the current thread (the while loop) will keep blocking forever.
Don't block. I don't see any reason to use a loop in the first place - instead, simply test to see if the counter is greater than the allowed number, and if it is, return:
$('#filter-form').submit(function (event) {
event.preventDefault();
alert("counter init = " + counter)
if (counter >= 10) return;
If you wanted to make multiple requests in parallel on form submit, you could do that, but you would have to keep track of the counter client-side:
var counter = 0;
$('#filter-form').submit(function (event) {
event.preventDefault();
alert("counter init = " + counter)
while (counter < 10) {
counter++;
// ... make request
As others have said your problem is that the call is asynchronous. This simple example may give you some idea about how to control the flow. It should be simple enough to apply it to your case.
I am simulating what you need to make your code work. For the errors, I am passing back null but you should bubble up any errors that may occur and either halt execution or deal with them some other way.
var count = 0; // used to store your count
// This represents the function you are
// waiting on with your ajax calls
function waitOne(num, callback) {
setTimeout(() => {
callback(null, num);
}, 1000);
}
// This represents your ajax call
function callWaitOne(callback) {
waitOne(count, (err, num) => {
// Your result is here
console.log(num);
// Callback to let the control function
// know the ajax has returned
callback(null);
});
}
// This will control the calls
function printWaitOne() {
callWaitOne((err) => {
if (count < 10) {
count++;
// Only calls if its callback
// has been called.
printWaitOne();
}
});
}
printWaitOne();
I am making a call to Google Calendar API for a specific event and am able to get the recurrence value.
function getRecurrence(payload) {
console.log('payload = ' + payload);
var recurrence = '';
if (payload != undefined) {
var requestRecurringEvent = window.gapi.client.calendar.events.get({
'calendarId': 'primary',
'eventId': payload
});
requestRecurringEvent.execute(function(resp) {
console.log('requestRecurringEvent = ' + resp);
console.log('requestRecurringEvent.recurrence = ' + resp.recurrence);
recurrence = resp.recurrence;
console.log('recurrence (inside execute)= ' + recurrence); //NO ISSUE (YET): recurrence (inside execute) = RRULE:FREQ=WEEKLY;COUNT=10
return recurrence;
});
} else {
return 'no recurrence value';
}
}
However, when I return the recurrence value to a variable
var recurrence = getRecurrence(action.payload.calendarEventRecurringEventId);
console.log('recurrence (outside execute) = ' + recurrence); //ISSUE: recurrence (outside execute) = undefined
note: action.payload.calendarEventRecurringEventId is because I am passing in the value from payload in redux.
Why is the value defined inside the api call but undefined outside the api call??
Thanks!
UPDATE
Promises seem to have taken me in the right direction, since the console.log('recurrence (outside execute) = ' + recurrence) prints a value, but it seems to not have completely solved the problem since an empty string is still being set for the value in redux.
var recurrencePromise = new Promise(function(resolve, reject) {
var payload = action.payload.calendarEventRecurringEventId;
console.log('action.payload.calendarEventRecurringEventId = ' + payload);
var recurrence = '';
if (payload != undefined) {
console.log('getRecurrence payload != undefined');
var requestRecurringEvent = window.gapi.client.calendar.events.get({
'calendarId': 'primary',
'eventId': payload
});
requestRecurringEvent.execute(function(resp) {
console.log('requestRecurringEvent = ' + resp);
console.log('requestRecurringEvent.recurrence = ' + resp.recurrence);
recurrence = resp.recurrence;
console.log('recurrence (inside execute)= ' + recurrence);
resolve(recurrence);
});
} else {
reject(Error("It broke"));
}
});
recurrencePromise.then(function(recurrence) {
console.log('recurrence (outside execute) = ' + recurrence);
var recurrenceTemp = 'RRULE:FREQ=WEEKLY;COUNT=10';
return {
...state,
calendarEventEndDate: action.payload.calendarEventEndDate,
calendarEventEndDateTime: action.payload.calendarEventEndDateTime,
calendarEventEndTime: action.payload.calendarEventEndTime,
calendarEventID: action.payload.calendarEventID,
calendarEventTitle: action.payload.calendarEventTitle,
calendarEventRecurringEventId: defaultIfUndefined(action.payload.calendarEventRecurringEventId, ''),
calendarEventRecurrence: recurrence, //ISSUE: this is where the value should be something like `RRULE:FREQ=WEEKLY;COUNT=10` instead of `''`
}
}, function(err) {
console.log(err); // Error: "It broke"
});
Why is the right value still not set defined outside the api call?
Because the requestRecurringEvent.execute function is asynchronous, which means you can never tell when the asynchronous function completes its execution (returns) unlike a synchronous function that guaranties a return value, async doesnt. What you need to do if you want to use the recurrence value outside is, A) Use a callback function B) use Promises (which I am sure the google api call provides by default)
I recommend you read more about asynchronity in javascript, to have a grasp on how it works.
Once again I struggle with ajax calls - this time around some chaining issue. Overall here is what I need to accomplish:
I loop over some array, and for each item in the array, I need to do the following:
Issue an Ajax call, and upon success, I need to issue three other calls, which must be chained, so they run in sequence.
When all the items in the array have both their main call and the three chained subcalls completed, I must be able to do some action.
My problem is, that the program does not wait for the three chained subcalls to complete. In the code below, this can be seen by the "Done" statement in the log turns up before the subcalls have completed.
I have created a JSFiddle here: https://jsfiddle.net/LeifFrederiksen/td534phz/1/
Note: I have two different function for the addAttachments function (addAttachments and addAttachmentsAlternative) - none of them works like they should.
var items = ["A","B"];
save();
function doneSaving() {
log("<H1>Done</H1>");
}
function save() {
// Save all items, and do something when all is done...
log("<H1>Save initiated</H1>");
var returnValue = saveItems();
$.when(returnValue).done(function() {
doneSaving();
})
}
function saveItems() {
// Loop through all items and save each of them...
var requests = Array();
// Build array of requests to wait for...
for (item of items) {
requests.push(saveOneItem(item));
}
var returnValue = $.when.apply($, requests).done(function() {
log("All requests completed");
})
return returnValue;
}
function saveOneItem(item) {
// Save one item...
return addListItem(item,addListItemSuccess,addListItemFailure);
}
function addListItem(item, successFunction, failureFunction) {
// The actual ajax that handles saving to database (actually Sharepoint via REST)...
log("addListItem on: " + item);
var returnValue =
$.ajax({
url: "/echo/json/",
data: {html: item,
delay: 1},
}).done(function (data) {
if (successFunction != undefined) {
returnValue = successFunction(item, data); // Returns the newly created list item information
return returnValue;
}
}).fail(function (data) {
return failureFunction(item, data);
});
return returnValue;
}
function addListItemSuccess(item,data) {
log("addListItem succces - in succes function for " + item);
returnValue = addAttachmentsAlternative(item,data);
return returnValue;
}
function addAttachments(item,data) {
var attachment1Deferred = addListItem(item + "-attachment 1",addAttachmentSuccess,addAttachmentFailure);
var attachment2Deferred = attachment1Deferred.then(
function() {
return addListItem(item + "-attachment 2",addAttachmentSuccess,addAttachmentFailure);
});
var attachment3Deferred = attachment2Deferred.then(
function() {
return addListItem(item + "-attachment 3",addAttachmentSuccess,addAttachmentFailure);
});
attachment3Deferred.done(
function() {
log("Completed upload of all attachments for " + item);
})
return attachment3Deferred;
}
function addAttachmentsAlternative(item,data) {
return addListItem(item + "-attachment 1",addAttachmentSuccess,addAttachmentFailure)
.done(function(data) {
return addListItem(item + "-attachment 2",addAttachmentSuccess,addAttachmentFailure)
}).done(function(data) {
return addListItem(item + "-attachment 3",addAttachmentSuccess,addAttachmentFailure)
}).done(function(data) {
log("Completed alternative upload of all attachments for " + item);
});
}
function addAttachmentSuccess(item,data) {
log("addAttachment succces - in succes function for " + item);
var deferred = $.Deferred();
deferred.resolve();
return deferred;
}
function addListItemFailure(item,data) {
console.log("addListItem failed - calling failure function for " + item);
$("#console").append("<P>addListItem failed - in failure function for " + item);
}
function addAttachmentFailure(item,data) {
console.log("addListItem failed - calling failure function for " + item);
$("#console").append("<P>addListItem failed - in failure function for " + item);
}
function log(message) {
console.log(message);
$("#console").append("<P>" + message);
}
I am hoping to achieve some generic pattern that I can use in different cases.
I got my inspiration from this great article, but cannot get it to work in my scenario: https://medium.com/coding-design/writing-better-ajax-8ee4a7fb95f#.tu0sruz5k
Any ideas and inputs are more than welcome.
Regards
Leif
There are several issues with the provided example:
to chain the tasks of creating list items and adding attachments use
.then instead of .done. With .done callback that prints All requests completed it is fired once deferred (first ajax call in addListItem function) is getting resolved.
some functions like addListItem still uses callback function syntax, i would suggest convert them to promises
since all deferred are getting resolved in saveItems function there is no need to use jQuery.when() in save function
Modified demo
I have a coding issue where I want to loop thru and call an ajax call but I dont want another request to be sent until the first one is complete. I have tried setting it to asyc = false and adding an onsuccess callback. But it seems like the loop is continuing to run which gives me responses out of order and parallel requests.
// This function is used to generate a numeric val and passes it along in the success callback
function duplicateOmsid(totalAmount, omsid) {
var url = '/portal/GetBulkCopyAmountServlet';
var errorString;
new Ajax.Request(
url, {
method: 'post',
parameters: {
totalAmount: totalAmount,
omsid: omsid
},
async: false,
onSuccess: function(transport) {
dataResponse = transport.responseText.evalJSON();
createWorkflow(totalAmount, omsid, dataResponse);
},
.....
// Function used to loop thru and call the duplicate workflow ajax call
function createWorkflow(totalAmount, omsid, bulkAmount) {
var amountProccessed = 0;
for( i = 0; amountProccessed < totalAmount; i++ ) { // Loop through source
var duplicateAmt;
if (totalAmount < 11){
duplicateAmt = totalAmount
}else{
duplicateAmt = amountProccessed + dataResponse < totalAmount ? dataResponse : totalAmount - amountProccessed
}
duplicateWorkflow(totalAmount, omsid, duplicateAmt, amountProccessed);
amountProccessed += bulkAmount;
}
}
// Function used to create the workflow ajax call - the success handler is updating the user.
function duplicateWorkflow( totalAmount, omsid, bulkAmount, amountProccessed){
amountProccessed += bulkAmount;
var url = '/portal/CreateWorkFlowServlet';
var errorString;
new Ajax.Request(
url, {
method: 'post',
parameters: {
totalAmount: totalAmount,
omsid: omsid,
bulkAmount: bulkAmount
},
async: false,
onSuccess: function(transport) {
var div = document.getElementById('progress');
if( amountProccessed > totalAmount){
div.innerHTML = totalAmount + ' out of ' + totalAmount + ' Processed ' ;
alert (totalAmount + 'Items successfully duplicated ')
}else{
div.innerHTML = amountProccessed + ' out of ' + totalAmount + ' Processed ' ;
}
},
onFailure: function(e) {
}
},
onException: function(e) {
}
},
});
}
As a rule of thumb, the way to sequentialize async code using raw Javascript is to use recursion instead of a for loop.
var urls = [ /*...*/ ];
function loop(i, onDone){
if(i >= urls.length){
//base case
onDone( theResultOfProcessingTheAjaxRequests );
}else{
Ajax.Request(urls[i], {
onsuccess: function(){
loop(i+1, onDone);
}
});
}
}
loop(0, function(result){
console.log("all done");
});
Note that I converted i to a function parameter, to keep it scoped to the looping function. If you wanted, you could declare it outside, just like you did in the for loop:
var urls = [ /*...*/ ];
var i = 0;
function loop(onDone){
//...
i = i+1;
loop(onDone);
}
Additionally, I added an "onDone" callback to the looping function to help the async code look a bit more like the sync version. The idea is that by using a return callback, the loop function doesn't need to know what function called it and where it should jump to after its done its job - in the end, calling onDone(x) is a bit similar to doing return x. Of course, you could have hardcoded the return function if you wanted.
function afterAjax(){
console.log("all done");
}
function loop(){
if(i >= urls.length){
afterAjax();
}
//...
}
loop();
Finally, coding recursive loops like this is a bit annoying and there are many libraries out there that provide functions to encapsulate these hight level sequentialization and parallelization patterns. In particular, error handling (try-catch) is specially hard to do by hand with callbacks. If you are doing any more non-tricial Async stuff I would highly recommend looking into some of these libraries.
I have the following javascript function, which uses .append from JQuery. However it only works if I add alert() at the beginning of the function. I have found out the reason for this behavior is due to the asynchronous way AJAX works.
How can I make sure that this function displays the html as wanted?
this.printJSON = function(id) {
//alert(id);
$(id).append('<button id="#store">store</button>');
for(key in params) {
$(id).append('<p '...'</p>');
}
};
my whole class, which is called this way:
params.parseJSON();
params.printJSON("#showdata");
function Parameters(parametersFile) {
//private stuff
var paramFile = parametersFile;
var params = {};
//public stuff
this.parseJSON = function() {
$.getJSON('inputFileParametersJSON.txt', function(json) {
for(var param in json) {
for(var key in json[param]) {
if(json[param].hasOwnProperty(key)) {
params[key] = {
filterIdentifier : json[param][key].filterIdentifier,
paramIdentifier : json[param][key].paramIdentifier,
param : json[param][key].param
};
}
}
}
});
};
this.printJSON = function(id) {
alert("");
$(id).append('<button id="#store">store</button>');
for(key in params) {
$(id).append('<p id="' + key + '"> filterIdentifier: ' + params[key].filterIdentifier + '<br /> paramIdentifier: ' + params[key].paramIdentifier + '<br /> param= <input type="text" id="' + key + '"name="param" value="' + params[key].param + '"/></p>');
//alert(params[key].filterIdentifier);
}
};
}
It looks like you may need to re-evaluate the way your functions are being called. Do you actually need to call them seperately? I'd rename parseJSON to something like getJSON
function Parameters(parametersFile) {
// ...
var printJSON = function(id) {
$(id).append('<button id="#store">store</button>');
for(key in params) {
// ...
}
};
//public stuff
this.getJSON = function(id) {
$.getJSON('inputFileParametersJSON.txt', function(json) {
// process results ...
printJSON(id);
});
};
}
This way you can simply call foo.getJSON('#someid') and it will not append until the request has been processed.
As jcm has said, you should be calling printJSON() from the response handler to enable it to work once the results of the request have been used to populate params.
Here be monsters
If you really need to wait for the result of an ajax post and can't use the result in a response handler (which is almost never), you can set async to false (see http://api.jquery.com/jQuery.ajax/), but since JS is executed in a single thread, this will halt execution of the JS until the request is completed.
Since you are using $.getJSON() you would need to use the ajaxSetup http://api.jquery.com/jQuery.ajaxSetup/ to change the behaviour of the ajax call.
You must make sure that .printJSON() is called after .parseJSON() finishes parsing the data. This can be done by directly calling .printJSON() (see answer by jcm) or by addind a support for callback that will get executed when .parseJSON is done.
function Parameters(parametersFile) {
var that = this;
// ...
this.parseJSON = function(callback) {
$.getJSON('inputFileParametersJSON.txt', function(json) {
for (var param in json) {
for (var key in json[param]) {
// ...
}
}
callback.call(that);
});
};
this.printJSON = function(id) {
// ...
}
};
// ...
parameters.parseJSON(function() {
// ...
// you can use "this" as if you were in Parameters
this.printJSON(someId);
// ...
});