my first post here, hi everyone :)
i have this js code to access a json api:
function a() {
//does some things
//...
//then calls function b
b(some_params);
}
function b(params) {
//calls an objects method that makes an ajax call to an api and get the response in json
someObject.makeajaxcalltoapi(params, function(response) {
alertFunction(response);
});
}
function alertFunction(resp) {
console.log ("the response is: ");
console.log(resp);
}
this is working ok, but now i need to modify it in order to do this:
in function a(), instead of making a single call to b(), i need to call b() multiple times in a loop, with different parameters each time.
and then i want to call alertFunction() passing it an array with all the responses, but only after all responses have been received.
i have tried to use $.when and .then, after seeing some examples on deferred objects, but its not working:
function a() {
//does some things
//...
//then calls function b
var allResponses = [];
$.when(
anArray.forEach(function(element) {
allResponses.push(b(some_params));
});
).then(function() {
alertFunction(allResponses);
});
}
function b(params) {
//calls an objects method that makes an ajax call to an api and get the response in json
someObject.makeajaxcalltoapi(params, function(response) {
//alertFunction(response);
});
return response;
}
function alertFunction(allresp) {
console.log ("the responses are: ");
console.log(allresp);
}
any help?
UPDATE - ok finally got it working. i put here the final code in case it helps somebody else...
function a() {
//does some things
//...
//then calls function b
var requests = [];
//-- populate requests array
anArray.forEach(function(element) {
requests.push(b(some_params));
});
$.when.apply($, requests).then(function() {
alertFunction(arguments);
});
}
function b(params) {
var def = $.Deferred();
//calls an objects method that makes an ajax call to an api and get the response in json
someObject.makeajaxcalltoapi(params, function(response) {
def.resolve(response);
});
return def.promise();
}
function alertFunction(allresp) {
console.log ("the responses are: ");
console.log(allresp);
}
Here is one way to use $.when with an unknown number of AJAX calls:
$(function () {
var requests = [];
//-- for loop to generate requests
for (var i = 0; i < 5; i++) {
requests.push( $.getJSON('...') );
}
//-- apply array to $.when()
$.when.apply($, requests).then(function () {
//-- arguments will contain all results
console.log(arguments);
});
});
Edit
Applied to your code, it should look something like this:
function a() {
var requests = [];
//-- populate requests array
anArray.forEach(function(element) {
requests.push(b(some_params));
});
$.when.apply($, requests).then(function() {
alertFunction(arguments);
});
}
function b(params) {
//-- In order for this to work, you must call some asynchronous
//-- jQuery function without providing a callback
return someObject.makeajaxcalltoapi();
}
function alertFunction(allresp) {
console.log ("the responses are: ");
console.log(allresp);
}
Related
I have this code as a starting point.
// $ = jQuery
// groupAdata and groupBdata are arrays
function funcA(elem) {
for (f = 0; f < groupAdata.length ; f++) {
// this is an example on how this function calls other functions asynchronously.
elem.children('.partyA').each( function() {
this.innerHTML = "been here" + groupAdata[f];
});
}
}
function funcB(elem) {
// another function that fires more calls
for (f = 0; f < groupAdata.length ; f++) {
$.post(url, somedata, function(data) {
elem.children('.partyB').each( function() {
this.innerHTML = "will be there" + groupBdata[f] + data;
});
}
}
}
$(document).ready(function() {
$('.groupA').each(function () {
funcA(this);
});
$('.groupB').each(function (){
funcB(this);
});
});
function endofitall() {
// call this after all instances of funcA and funcB are done.
}
When running endofitall(), I'd like to be sure that all calls of funcA and funcB are done.
I take that Promises and jQuery.Deferred() would be a good/preferred approach but was not able to map the answers I found to this specific scenario. (It is part of a templating tool that fires multiple dom manipulators func[AB] for multiple DOM elements.)
You can use $.when().
Your goal should be to get to:
// call funcA, call funcB
$.when( funcA(), funcB() )
// when everything is done go on with the callback
.done(endofitall);
In the case of funcA (synchronous function there's no problem and it will work as is).
In the case of funcB (asynchronous) there are some things to consider. If it would be just one ajax call your code should be something like:
// This function returns a promise.
// When it's fulfilled the callback (in your case '.done(endofitall)')
// will be called.
function funcB(somedata){
return $.post(url, somedata);
}
As you are actually making more requests you have to return a resolved promise only when all calls have been fulfilled.
// an *Asynchronous* function, returns an array of promises
function funcB(elem, groupAdata) {
var allCalls = [];
// for each element in the array call the relative async
// function. While you're calling it push it to the array.
groupAdata.forEach(data, function(data){
allCalls.push( $.post(url, data) );
});
// allCalls is now an array of promises.
// why .apply(undefined)? read here: https://stackoverflow.com/a/14352218/1446845
return $.when.apply(undefined, allCalls);
}
At this point you can go for a flat and clear:
$.when( funcA(), funcB() ).done(endofitall);
As a rule of thumb: if you are making async requests try to always return a promise from them, this will help flatten out your code (will post some link later on if you want) and to leverage the power of callbacks.
The above code can surely be refactored further (also, I haven't used a lot of jQuery in the last few years, but the concept applies to any Js library or even when using no library at all) but I hope it will help as a starting point.
References:
$.when
A similar answer here on SO
Call endofitall() inside each iteration for funcA and funcB. Keep a counter and perform the actual work once the counter reaches the number signifying all the tasks are complete.
function funcA(elem) {
for (f = 0; f < groupAdata.length ; f++) {
// these calls are not async
elem.children('.partyA').each( function() {
this.innerHTML = "been here" + groupAdata[f];
});
endofitall();
}
}
function funcB(elem) {
// another function that fires more calls
for (f = 0; f < groupBdata.length ; f++) {
$.post(url, somedata, function(data) {
elem.children('.partyB').each( function() {
this.innerHTML = "will be there" + groupBdata[f] + data;
});
endofitall();
}
}
}
$(document).ready(function() {
$('.groupA').each(function () {
funcA(this);
});
$('.groupB').each(function (){
funcB(this);
});
});
var counter=0;
function endofitall() {
if(++counter==groupAdata.length + groupBdata.length){
//do stuff
}
I have to call 3 functions with AJAX requests before one of the functions can be finished. All functions needs the same data, so I want to start the AJAX request only once. I think that I need a functionality to call 2 of the 3 functions to wait and provide the data at the end. Maybe the problem is that I am new to jQuery Deferred and dont find some basic stuff? Thanks for help!
Because my script is to complex as example so I created this demo: (I hope it is self explanatory)
<script>
var requestRunning = false;
//do some ajax request etc...
function doSomething() {
return {
doIt: function (test, startDelay) {
var dfd = $.Deferred();
setTimeout(function () {
if (requestRunning == false) {
console.log("starting ajax call:", test);
requestRunning = true;
//Fake ajax call
setTimeout(function () {
dfd.resolve(test);
// Todo: A done; provide data to waiting B and C.
}, 500);
}
else {
console.log("ajax call allready running, waiting...", test);
}
}, startDelay);
return dfd.promise();
}
}
}
// Fake delay for function calls in really short time
var master = doSomething();
var a = master.doIt("a", 10);
var b = master.doIt("b", 15);
var c = master.doIt("c", 12);
// Do some stuff with the received data...
a.done(function myfunction(result) {
console.log(result + " done");
});
b.done(function myfunction(result) {
console.log(result + " done");
});
c.done(function myfunction(result) {
console.log(result + " done");
});
</script>
I'm not entirely sure what you are trying to do, but if what you want to do is to start three ajax calls at once and then know when all of them are done, since jQuery ajax calls already return a promise, you can use that promise and $.when() like this:
var p1 = $.ajax(...);
var p2 = $.ajax(...);
var p3 = $.ajax(...);
$.when(p1, p2, p3).then(function(r1, r2, r3) {
// results of the three ajax calls in r1[0], r2[0] and r3[0]
});
Or, you can even do it without the intermediate variables:
$.when(
$.ajax(...),
$.ajax(...),
$.ajax(...)
).then(function(r1, r2, r3) {
// results of the three ajax calls in r1[0], r2[0] and r3[0]
});
If you are calling functions that themselves do ajax calls, then you can just return the ajax promise from those functions and use the function call with the structure above:
function doSomethingAjax() {
// some code
return $.ajax(...).then(...);
}
$.when(
doSomethingAjax1(...),
doSomethingAjax2(...),
doSomethingAjax3(...)
).then(function(r1, r2, r3) {
// results of the three ajax calls in r1[0], r2[0] and r3[0]
});
function hello() {
var arr = [];
$.get(url, function (data) {
var items = $(data).find("item");
$(items).each(function (idx, item) {
arr.push(item);
});
});
return arr; //undefined because nested loops are not finished processing.
}
How do I make sure that arr is populated before returning it?
There is no way to escape from asynchronous calls. You would need callbacks to get the result of the GET call.
function asynCall() {
var response;
// Ajax call will update response here later.
return response;
}
var responseFromFun = asyncCall(); // This will be undefined or null.
This is how your code works now. So response will always be undefined or null.
To get the response from Ajax calls pass a callback to the function when invoking it instead of assigning a response to it.
function asyncCall(callBack) {
var response;
$.get(...) {
response = someValueReturnedFromServer;
callBack(response);
}
// There wont be a return here
}
asyncCall(function(response){
// Do something with response now
});
The downside here is that if you are passing arr object (in your code) to some other function even that has to be changed to use callbacks !
var ajaxStuff = (function () {
var doAjaxStuff = function() {
//an ajax call
}
return {
doAjaxStuff : doAjaxStuff
}
})();
Is there any way to make use of this pattern, and fetch the response from a successful ajaxcall when calling my method? Something like this:
ajaxStuff.doAjaxStuff(successHandler(data){
//data should contain the object fetched by ajax
});
Hope you get the idea, otherwise I'll elaborate.
Two things:
1. Add a parameter to the doAjaxStuff function.
2. When invoking doAjaxStuff, pass in an anonymous function (or the name of a function)
var ajaxSuff = (function () {
var doAjaxStuff = function(callback) {
// do ajax call, then:
callback(dataFromAjaxCall);
}
return {
doAjaxStuff : doAjaxStuff
}
})();
// calling it:
ajaxStuff.doAjaxStuff(function(data){
//data should contain the object fetched by ajax
});
Just let doAjaxStuff accept a callback:
var doAjaxStuff = function(callback) {
// an ajax call
// Inside the Ajax success handler, call
callback(response); // or whatever the variable name is
}
Depending on your overall goals, you could also use deferred objects instead (or in addition). This makes your code highly modular. For example:
var doAjaxStuff = function() {
// $.ajax is just an example, any Ajax related function returns a promise
// object. You can also create your own deferred object.
return $.ajax({...});
}
// calling:
ajaxStuff.doAjaxStuff().done(function(data) {
// ...
});
I believe you need to read the jQuery docs for jQuery.ajax. You could make a call as simple as:
$.ajax('/path/to/file').success(function (data) {
doStuff();
})
I'm trying to figure out how to accomplish this workflow, but can't seem to nail it. I've got n number of <select> elements on a page. When the page loads, for each <select> element, I need to make a $.get(...); call. Once all of those calls are done, then, and only then do I need to run an additional function. Here is some example code to better explain:
function doWork(selectEl) {
var getData = ...; // build request data based on selectEl
$.get('/foo/bar', getData, function (data) {
// Do something to selectEl with the result
});
}
function doMoreWork() {
// Do something with all the selects now that they are ready
}
$(function () {
// For each of the select elements on the page
$('select').each(function(index, selectEl) {
// Go do some AJAX-fetching of additional data
doWork(selectEl);
});
// Once *all* the $.get(...) calls are done, do more things
doMoreWork();
});
Using the code above, doMoreWork() is usually called before all of the async $.get(...); calls have had a chance to return; which is not what I want. I need to have all of the $.get(...); calls complete before doMoreWork() can be called. Basically I need a callback of sorts to execute once ALL of the $.get(...); calls in the above example have finished.
How would I go about accomplishing this?
Every time you call doWork, increment a counter.
Every time a response comes back, decrement the counter.
Have the callback invoke doMoreWork when the counter reaches 0.
var counter = 0;
function doWork(selectEl) {
counter++;
var getData = ...; // build request data based on selectEl
$.get('/foo/bar', getData, function (data) {
counter--;
if( !counter ) { doMoreWork(); }
});
}
function doMoreWork() {
// Do something with all the selects now that they are ready
}
$(function () {
// For each of the select elements on the page
$('select').each(function(index, selectEl) {
// Go do some AJAX-fetching of additional data
doWork(selectEl);
});
});
I would write a class something like:
function synchronizer(query, action, cleanup) {
this.query = query;
this.action = action;
this.cleanup = cleanup;
this.remaining = query.length;
this.complete = function() {
this.remaining -= 1;
if (this.remaining == 0) { this.cleanup(query); }
}
this.run = function() {
query.each(function(index, which) { action(which, this.complete); })
}
}
// Aargh. Expecting doWork() to call a passed-in continuation seems ugly to me
// as opposed to somehow wrapping doWork within the synchronizer... but I can't
// think of a way to make that work.
function doWork(element, next) {
var getData = ...; // build request data based on element
$.get('/foo/bar', getData, function(data) {
// Do something to element with the result, and then
next();
});
}
function doMoreWork(elements) {
// Do something with all the selects now that they are ready
}
new synchronizer($('select'), doWork, doMoreWork).run();
Keep track of how many Ajax calls have yet to complete, and execute doMoreWork() when there are none left.
$(function(){
var workLeft = $('select').length;
function doWork(selectEl) {
var getData = ...; // build request data based on selectEl
$.get('/foo/bar', getData, function (data) {
// Do something to selectEl with the result
// If done all work
if(!(--workLeft)){
doMoreWork();
}
});
}
function doMoreWork() {
// Do something with all the selects now that they are ready
}
// For each of the select elements on the page
$('select').each(function(index, selectEl) {
// Go do some AJAX-fetching of additional data
doWork(selectEl);
});
});
You may also want to catch ajax errors.
You can use jQuery's $.when to join together multiple Deferred objects to one:
$.when.apply($, $('select').map(function(index, selectEl) {
return $.ajax(....);
}).get()).done(function() {
// All AJAX calls finished
});
Basically, $.when takes multiple Deferred objects as each argument and wraps them together as one Deferred by keeping track of the number of completed sub-deferres, similar to how a couple of the answers here implemented it manually.
A more readable version of the above code is:
var requests = [];
$('select').each(function(index, selectEl) {
request.push($.ajax(....));
}
$.when.apply($, requests).done(function() {
// All AJAX calls finished
});
Maybe you could use the JavaScript underscore library's after function.
(note: I haven't tested this code)
var numberOfSelectElements = n;
var finished = _after(numberOfSelectElements, doMoreWork);
function doWork(selectEl) {
var getData = ...; // build request data based on selectEl
$.get('/foo/bar', getData, function (data) {
finished();
});
}
function doMoreWork() {
// Do something with all the selects now that they are ready
}
$(function () {
// For each of the select elements on the page
$('select').each(function(index, selectEl) {
// Go do some AJAX-fetching of additional data
doWork(selectEl);
});
});
Use Deferred:
function doWork(selectEl) {
var getData = ...;
// return Deferred object
return $.get('/foo/bar', getData, function (data) {
});
}
var selects = $('select');
function doItem(i) {
if(selects.length === i) return doMoreWork(); // if no selects left, abort and do more work
$.when(doWork(selects.get(i)).then(function() { // fetch and do next if completed
doItem(i + 1);
});
});
doItem(0); // start process
Since it looks like you're doing jQuery, you could use the $.ajaxStop event handler...
http://api.jquery.com/ajaxStop/
EDIT Said $.ajaxComplete instead of the correct $.ajaxStop... Fixed now...