jQuery deferrend; callback for waiting functions - javascript

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]
});

Related

jQuery Deferred not executing asynchronously with getJSON call

My problem is b() executing lastly, but it should be 2nd in the order. I want to call a JSON api on every click on a button (and always send new api call), but it should remove the last api message from the div:
$(document).ready(function() {
function test() {
function a() {
$("#message-quote, #message-person").empty();
console.log('p1');
};
function b() {
console.log('p2 - before getJSON');
$.getJSON("http://quotesondesign.com/wp-json/posts?filter[orderby]=rand&filter[posts_per_page]=1&callback=", function(a) {
$("#message-quote").append(a[0].content)
$("#message-person").append("<p>— " + a[0].title + "</p>")
console.log('p2 - in getJSON');
});
};
function c() {
$("body, .button, .quote-icons a").css("background-color", "red");
$(".quote-text").css("color", "red");
console.log('p3');
};
var d = $.Deferred(),
p = d.promise();
p.then(a()).then(b()).then(c());
d.resolve();
};
$("#getMessage").on("click", function() {
test();
});
});
The order I got is:
"p1"
"p2 - before getJSON"
"p3"
"p2 - in getJSON"
Your not waiting for the return of b(), you need to chain the promises due to it calling Asynchronously.
if you return a promise out of b(), it should then wait for result of getJSON to be return before it continues.
also, your functions are executing straight away, .then(callback) needs to take a function it can execute later, where your executing a function straight away instead
function b() {
return $.getJSON("http://quotesondesign.com/wp-json/posts?filter[orderby]=rand&filter[posts_per_page]=1&callback=", function(a) {
$("#message-quote").append(a[0].content)
$("#message-person").append("<p>— " + a[0].title + "</p>")
console.log('p2');
// resolve the promise, the call has been completed
});
};
var d = $.Deferred(),
p = d.promise();
p.then(a)
.then(b)
//the context of .then will now be the promise out of b()
.then(c);
d.resolve();
Note : Promises will need to also be returned form each other method.
EDIT: as Rocket pointed out, jQuery Ajax calls implement the promise methods
working example : https://jsfiddle.net/rsfxpg38/4/
you can see an example of it from this post :
How do I chain a sequence of deferred functions in jQuery 1.8.x?

Chaining callbacks in a custom-made for-each loop, supporting both synchronous and asynchronous functions

I have a for_users function that gets an array of users from a web service, executes a passed function f on the received array, then calls a continuation f_then callback.
// Execute f on every user, then f_then.
function for_users(f, f_then)
{
// Get all users from the database, in user_array
db.get_all_users(function(user_array)
{
// Execute f on every user
user_array.forEach(f);
// Call continuation callback
f_then();
});
}
When calling for_users, passing an asynchronous function as the f parameter, I would like all the f callbacks to end before calling f_then. This is obviously not happening in the current code, as user_array.forEach(f) does not wait for f to finish before starting the next iteration.
Here's an example of a problematic situation:
function example_usage()
{
var temp_credentials = [];
for_users(function(user)
{
// Get credentials is an asynchronous function that will
// call the passed callback after getting the credential from
// the database
database.get_credentials(user.ID, function(credential)
{
// ...
});
}, function()
{
// This do_something call is executed before all the callbacks
// have finished (potentially)
// temp_credentials could be empty here!
do_something(temp_credentials);
});
}
How can I implement for_users such that if f is an asynchronous function, f_then is called only when all f functions are completed?
Sometimes, though, the passed f to for_users is not asynchronous and the above implementation could suffice. Is there a way to write a generic for_users implementation that would work as intended both for asynchronous and synchronous f functions?
this should work for you:-
function for_users(f, f_then) {
db.get_all_users(function(user_array) {
var promises = [];
user_array.forEach(function(user) {
promises.push(new Promise(function(resolve, reject) {
f(user);
resolve();
}));
});
if (f_then)
Promise.all(promises).then(f_then);
else
Promise.all(promises);
}
});
}
Simple Test below:-
function for_users(f, f_then) {
var users = [{ID: 1}, {ID: 2}, {ID: 3}];
var promises = [];
users.forEach(function(user) {
var promise = new Promise(function(resolve, reject) {
f(user);
resolve();
});
promises.push(promise);
})
if (f_then)
Promise.all(promises).then(f_then);
else
Promise.all(promises)
}
for_users(function(user) {
console.log(user.ID);
}, function() {
console.log('finshed')
})
You can add next continuation callback to f function like this:
function for_users(f, f_then) {
// Get all users from the database, in user_array
db.get_all_users(function(user_array) {
// Execute f on every user
(function recur(next) {
var user = user_array.shift();
if (user) {
f(user, function() {
recur(next);
});
} else {
// Call continuation callback
next();
}
})(f_then);
});
}
and then you will be able to call this function using this:
for_users(function(user, next) {
// Get credentials is an asynchronous function that will
// call the passed callback after getting the credential from
// the database
database.get_credentials(user.ID, function(credential) {
next();
});
}, function() {
// This do_something call is executed before all the callbacks
// have finished (potentially)
// temp_credentials could be empty here!
do_something(temp_credentials);
});
var getCredentials = function(step){
return function(user){
database.get_credentials(user.ID, function(credential) {
step(credential);
});
};
};
var allFinish = function(f){
return function(step) {
return function(arr){
var finished = 0;
var values = new Array(arr.length);
if(arr.length){
arr.forEach(function(el, i){
f(function(value){
if(finished === arr.length){
step(values);
} else {
values[i] = value;
finished++;
}
})(el);
});
} else {
step(values);
}
};
};
};
var forEachUser = function(doSomething){
db.get_all_users(allFinish(getCredentials)(doSomething));
}
And then you can just simply do:
forEachUser(function(tempCredentials){
//tempCredentials === allCredentials
});
There's probably better ways to handle the order of values inserted in the array in allFinish. allFinish works by taking a function that takes a step and calling it with a step function that will call another step function when all calls are finished. I curried the functions, but it isn't really necessary. It's just a convenience.

Best Practices to wait for multiple calls

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
}

javascript multiple ajax calls, single callback?

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);
}

Using deferred to chain loops with multiple ajax calls

There are multiple questions that already have an answer about this, but all aren't working so far it this kind of setup.
function login(u,p) {
console.log(1);
return $.post(url, {u,p});
}
function out() {
console.log(3);
//a function that does not return deferred
// clear cookies
}
function doSomething() {
console.log(2);
// a function that returns a deferred
return $.post(...);
}
var data = [{u: 'au', p: 'ap'}, {u: 'bu', p: 'bp'}]
$.each(data, function(k,v){
login(v.u, v.p).then(doSomething).then(out);
});
I was expecting it's sequence to be like:
1
2
3
1
2
3
But I get
1
2
1
3
2
3
Why is it like that, even though I am waiting for the promise to be resolve using then
If you want the logins to run synchronously:
var p = new jQuery.Deferred();
$.each(data, function(k,v){
p.then(function() {
return login(v.u, v.p);
}).then(doSomething).then(out);
});
Each new item iterated over in $.each won't trigger a new response until p finishes the last one.
The idea is to create a recursive function like #Popnoodles have mentioned.
e.g.
function a() {
return $.post();
}
function b() {
return $.post();
}
function c() {
console.log('no promise.');
}
// and the recursive main function
function main() {
if(counter < data.length){
$.when(a().then(b).then(c)).done(function(){
counter++;
main();
});
}
}
main();
Here is how it works, open the console to see how it logs the function in sequence.

Categories

Resources