Best Practices to wait for multiple calls - javascript

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
}

Related

NodeJS recursive call inside for loop, how to know when all calls are done?

I am writing a recursive function inside for loop like below:
var output = [];
function myFunc(myValue, callback) {
myAnotherFunc(myValue, function(result){
for (var i=0; i < result.myKey.length; i++){
if(result.myKey[i].name === 'something'){
myFunc(result.myKey[i].recurseValue, function(recursiveResult){
//some recursive stuff
output.push(recursiveResult.someValue)
});
}
}
});
}
And initiating the recursive function like below:
myFunc(initialValue, function(result){
//some stuff
});
Its working fine, but how do I know when my recursive flow ends so that I can do something else from the final output?
You can use Promises™! It's basically a way to defer a callback till after an Asynchronous flow is completed: Example:
// Instead of passing your normal callback, we'll tell the
// function to use resolve(results) to pass your results to the
// next code block so it can do something after all your recursions are completed
const someTask = new Promise(resolve => myFunc(initialValue, resolve))
someTask.then(result => {
/* Do Something with the results at the end of aformentioned callback hell :D */
})
PS. You also have to modify your original function signature to:
function myFunc(myValue, callback) {
myAnotherFunc(myValue, function(result){
const cbks = [] //Store the async resuls of all myFunc() executions
for (i=0; i < result.myKey.length; i++){
if(results[i] === 'something'){
cbks.push(new Promise(res => myFunc(result[i].recurseValue, res)))
}
}
//Run all async myFunc() and return the results in an array
Promise.all(cbks).then(callback)
});
}
function myFunc(resolve) {
var rec = function(myVal, cb) {
myOther(recurseValue, function(result) {
var hasName = result[myKey].filter(function(obj) {
return obj.name === 'something';
})[0];
if (hasName) {
rec(hasName[recurseValue], function(recResult) {
// other recursive stuff
});
} else {
resolve(?); // whatever the final value should be
}
});
};
return rec;
}
function recurseAsync(f, initial) {
return new Promise(function(resolve, reject) {
f(resolve)(initial);
});
}
Couple notes.
The recurseAsync function takes a function that takes a resolution callback and returns a recursive function that calls that callback when finished to resolve the promise. myFunc has been altered to fit that format.
I used array filtering rather than a for loop and shortened some names. Also if you are using a variable for object access use [] instead of .. To use the final value when all of this is finished you can call .then on the promise.
// whatever initial value 'foo' should be
var finished = recurseAsync(myFunc, foo);
finished.then(function(finalValue) {
// do something with the final result of all the recursion
});

How to do a "for" loop with asynchronous condition in Javascript?

I have this function:
waitForFreeAccnt.prototype.isMemberFree = function () {
var self = this;
self.api.getMemberInfo(function () {
var accType = self.api.connect.accountType;
console.log(accType);
if (accType === 'FREE') {
console.log('it is free');
return true;
} else {
console.log('it is not free');
return false;
}
});
};
I would like to wait till the account is free for up to 10 seconds with something like that:
var test = function () {
for (var start = 1; start < 10; start++) {
var result = self.isMemberFree();
console.log(result);
if (result) {
break;
} else {
self.api.pause(1000);
console.log('waiting');
}
}
};
But it doesn't work because self.api.getMemberInfo is asynch call. This is super frustrating with Javascript. Any other language it would be so simple to do. How do I force the for loop to wait for self.isMemberFree() to finish executing before proceeding with the loop?
Also to note, this is not in browser execution so I don't care about anything hanging.
When dealing with asynchronous code, you need to make use of callbacks. That is, if you want to do a() and b() in order but a() does something asynchronously, then you need to call b() from within a() once a() has a result. So not:
a(); // does something asynchronously
b(); // tries to use a()'s result but it isn't available yet
... but rather
a(b); // pass b to a() and a() will call it when ready
function a(callback) {
triggerAsyncFunction(function(result) {
if (result === something)
callback("a just finished");
});
}
Note that a() doesn't refer to b() by name, it just calls whatever function is passed in as an argument.
So applying that to your code, maybe something like this:
waitForFreeAccnt.prototype.isMemberFree = function (cbf) {
var self = this;
self.api.getMemberInfo(function () {
cbf(self.api.connect.accountType === 'FREE');
});
};
waitForFreeAccnt.prototype.testMemberXTimes = function(maxAttempts, callback) {
var attempts = 0;
var self = this;
(function attempt() {
self.isMemberFree(function(free) {
if (free)
callback(true);
else if (++attempts < maxAttempts)
setTimeout(attempt, 1000);
else
callback(false);
});
)();
};
this.testMemberXTimes(10, function(isFree) {
// the next part of your code here, or called from here
// because at this point we know we've tested up to
// ten times and isFree tells us the result
});
Note that the way I coded getMemberInfo() it is basically doing the same thing yours was, but instead of returning a boolean it is calling the callback function and passing the same boolean value that you were returning. (I've removed the console.log()s to make the code shorter.)
Note also that you could structure the above to use promises, but the end result will be the same.
You could return a Promise
waitForFreeAccnt.prototype.isMemberFree = function () {
return new Promise((reject, resolve)=>
// set a timeout if api call takes too long
var timeout = setTimeout(()=> reject(Error('API timeout')), 10000);
// make api call
this.api.getMemberInfo(()=> {
clearTimeout(timeout);
resolve(this.api.connect.accountType === 'FREE');
});
);
};
Then use it like this
whatever.isMemberFree().then(isFree=> {
if (isFree)
console.log('it is free');
else
console.log('it is not free');
})
// handle timeout or other errors
.catch(err=> {
console.log(err.message);
});
Building on naomik's answer, if you do it that way you can pretty easily use a for loop with it, using the (most likely) upcoming async/await feature - though it's not part of ES2015.
// Note "async" here! That will make "await" work. It makes the function
// return a promise, which you'll be able to either "await" or
// "test().then" later.
var test = async function () {
for (var start = 1; start < 10; start++) {
// Right here we're using "await" - it makes JavaScript *wait* for
// the promise that comes from self.isMemberFree() to be finished.
// It's really handy because you can use it in loops like "for" and
// "while" without changing the flow of your program!
var result = await self.isMemberFree();
console.log(result);
if (result) {
break;
} else {
self.api.pause(1000);
console.log('waiting');
}
}
};
For now you'll need to use a transpiler like Babel or Traceur before you can really use async/await, though. It's only supported in Microsoft Edge 14 right now.
And a big emphasis that what is returned from test() isn't whatever you directly return from inside it. If I do this:
var test = async function() { return 15; };
var result = test();
I'm not going to get 15 - I'll get a promise that will resolve as 15:
result.then(function(res) {
console.log(res); // 15
});
// or, use an async function again:
var main = async function() {
console.log(await res); // 15
};
main();
I don't have my work laptop today because it is Sunday, I'm coding this on sublime. Apologise if the syntax is a bit off.
To solve your problem I would recommend changing isMemberFree() to take in a callback function. This is because isMemberFree is async, and you will need a way to report the result after it has done the work.
Then change test function to use setTimeout API to wait a second.
Wrap the function call for isMemberFree() to be in a nested function and call it recursively, that way you'll have synchronize control over the async calls.
Look at the coding example:
waitForFreeAccnt.prototype.isMemberFree = function (done) {
var self = this;
self.api.getMemberInfo(function () {
var accType = self.api.connect.accountType;
console.log(accType);
if (accType === 'FREE') {
console.log('it is free');
return done(null, true);
} else {
console.log('it is not free');
return done(null, false);
}
});
};
var test = function () {
var testMembership = function(waitAttempt, isFree) {
if (isFree) {
return;
}
else if (waitAttempt > 10) {
// wait exceeded, do something.
return;
}
setTimeout(function() {
self.isMemberFree(function(err, isFree) {
testMembership(waitAttempt+=1, isFree);
});
}, /*total milliseconds in 1sec=*/1000);
}
testMembership(/*WaitAttempts=*/0, /*isFree=*/false);
};
What the above code does is that, presumably something has already been done to the member's account and now test function is called. So it waits for 1 second, then call isMemberFree function, this happens recursively until either isMemberFree() returns true OR the 10 seconds wait has been exceeded.

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

Increment for only after the previous interaction has been finished (callback)

I'm having a problem with callback functions in javascript. What I want to do is: loop on a for and call a function passing i as parameter. With that in mind, I have to loop to the next interaction only after the previous one has been finished. I don't know if this is a problem but inside the function I'm sending i as parameter, I have another callback function. Here is my code:
for(i=0; i<10; i++) {
aux(i, function(success) {
/*
* this should be made interaction by interaction
* but what happens is: while I'm still running my first interaction
* (i=0), the code loops for i=1, i=2, etc. before the response of
* the previous interaction
*/
if(!success)
doSomething();
else
doSomethingElse();
});
}
function aux(i, success) {
... //here I make my logic with "i" sent as parameter
getReturnFromAjax(function(response) {
if(response)
return success(true);
else
return success(false);
});
});
function getReturnFromAjax(callback) {
...
$.ajax({
url: myUrl,
type: "POST",
success: function (response) {
return callback(response);
}
});
}
jQuery's Deferred can be a bit tricky to get right. What you'll have to do is stack your promises in a chain. For example:
var
// create a deferred object
dfd = $.Deferred(),
// get the promise
promise = dfd.promise(),
// the loop variable
i
;
for(i = 0; i < 10; i += 1) {
// use `then` and use the new promise for next itteration
promise = promise.then(
// prepare the function to be called, but don't execute it!
// (see docs for .bind)
aux.bind(null, i, function(success) {
success ? doSomethingElse() : doSomething();
})
);
}
// resolve the deferred object
dfd.resolve();
for this to work, aux must also return a promise, but $.ajax already does this, so just pass it through and everything should work:
in aux:
function aux(i, callback) {
console.log('executing for `aux` with', i);
// return the ajax-promise
return getReturnFromAjax(function(response) {
callback(Boolean(response));
});
}
in getReturnFromAjax:
function getReturnFromAjax(callback) {
// return the ajax-promise
return $.ajax({
url: '%your-url%',
type: '%method%',
success: function (response) {
callback(response);
}
});
}
demo: http://jsbin.com/pilebofi/2/
I'd suggest that you'd look into jQuery's Deferred Objects and jQuery.Deferred()-method instead of making your own callback queue functions (as you are already using jQuery anyway).
Description: A constructor function that returns a chainable utility
object with methods to register multiple callbacks into callback
queues, invoke callback queues, and relay the success or failure state
of any synchronous or asynchronous function.
I don't have experience with jQuery, but your callback looks a bit fishy to me.
In plain JS I'd suggest trying something among the lines of this:
function yourMainFunction
{
function callbackHandler(result)
{
// Code that depends on on the result of the callback
}
getAjaxResults(callbackHandler);
}
function getAjaxResults(callbackHandler)
{
// Create xmlHttpRequest Handler, etc.
// Make your AJAX request
xmlHttp.onreadystatechange = function()
{
if (xmlHttp.readyState == 4 && xmlHttp.status==200)
{
// Do stuff you want to do if the request was successful
// Define a variable with the value(s) you want to return to the main function
callbackHandler(yourReturnVariable);
}
}
}

How would I defer this javascript call?

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...

Categories

Resources