I have a situation where i am running a jquery function and in that function i have an each loop. The each loop takes some time for processing and that is why the next statement executes before each completes. This creates a problem for me. I want a function to execute when each completes. My function is as follows:-
function myFunc() {
// Do something
$.each(mylist, function (i, val) {
// do something
filepicker.store(myList, function (stored_fpfile) {
console.log("Store successful:", JSON.stringify(stored_fpfile));
}, function (FPError) {
// Error
}, function (progress) {
console.log("Loading: " + progress + "%");
}
);
});
CallMyFunction();
}
Call my function executes before each loop finishes.
I dont want to use count of the list to detect and run my procedure. I want a reliable solution.
I am using the InkFilePicker API to store files to Amazon
Any help is appreciated.
I would suggest generating deferred objects for each iteration that are then stored in an array. Then, after the each, you can wait until all those deferred objects are complete to run your other function.
function myFunc() {
// Do something
var promiseArray = $.map(mylist, function (i, val) {
return $.Deferred(function(def){
// do something
filepicker.store(val, function (stored_fpfile) {
def.resolve(stored_fpfile);
}, function (FPError) {
def.reject(FPError);
}, function (progress) {
def.notify(progress); // won't actually be accurate
});
}).promise();
});
$.when.apply($,promiseArray).done(function(){
console.log(arguments); // array of results from onSuccess callbacks
CallMyFunction();
})
}
Apparently, the store is a wrapper around an AJAX object. You should test the progress and when it is completed call CallMyFunction, like so before the call to each:
items = myList.length;
And inside each:
...
function (progress) {
console.log("Loading: " + progress + "%");
if (--items === 0)
{
CallMyFunction();
}
}
...
There aren't any counter arguments, performance is not impacted by this, you're sending data over the Internet which is the real bottleneck.
This is also reliable. In case of error you should decrement with --items too.
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
}
Is there a callback for when underscore is finished it's _.each loop because if I console log immediately afterwards obviously the array I am populating with the each loop is not available. This is from a nested _.each loop.
_.each(data.recipe, function(recipeItem) {
var recipeMap = that.get('recipeMap');
recipeMap[recipeItem.id] = { id: recipeItem.id, quantity: recipeItem.quantity };
});
console.log(that.get('recipeMap')); //not ready yet.
The each function in UnderscoreJS is synchronous which wouldn't require a callback when it is finished. One it's done executing the commands immediately following the loop will execute.
If you are performing async operations in your loop, I would recommend using a library that supports async operations within the each function. One possibility is by using AsyncJS.
Here is your loop translated to AsyncJS:
async.each(data.recipe, function(recipeItem, callback) {
var recipeMap = that.get('recipeMap');
recipeMap[recipeItem.id] = { id: recipeItem.id, quantity: recipeItem.quantity };
callback(); // show that no errors happened
}, function(err) {
if(err) {
console.log("There was an error" + err);
} else {
console.log("Loop is done");
}
});
Another option is to build your callback function into the each loop on the last execution:
_.each(collection, function(model) {
if(model.collection.indexOf(model) + 1 == collection.length) {
// Callback goes here
}
});
Edit to add:
I don't know what your input/output data looks like but you might consider using _.map instead, if you're just transforming / rearranging the contents
I'm using an anonymous function to perform some work on the html I get back using Restler's get function:
var some_function() {
var outer_var;
rest.get(url).on('complete', function(result, response) {
if (result instanceof Error) {
sys.puts('Error: ' + result.message);
} else {
var inner_var;
// do stuff on **result** to build **inner_var**
outer_var = inner_var;
}
});
return outer_var;
}
How can I get the value of inner_var out to the some_function scope and return it? What I have written here doesn't work.
The get call is asynchronous, it will take some time and call your callback later. However, after calling get, your script keeps executing and goes to the next instruction.
So here is what happens:
you call get
you return outer_var (which is still undefined)
... sometimes later ...
get result has arrived and the callback is called.
outer_var is set
You can't have your some_function return a value for something that is asynchronous, so you will have to use a callback instead and let your code call it once data is processed.
var some_function(callback) {
rest.get(url).on('complete', function(result, response) {
if (result instanceof Error) {
sys.puts('Error: ' + result.message);
} else {
var inner_var;
// do stuff on **result** to build **inner_var**
callback(inner_var);
}
});
}
Like most of the modules of node, Restler is also a event based library having async calls. So at the time you do return outer_var; the complete callback was not necessarily called (With some libraries it could be if it the result was cached, but you should always expect that it is called asynchronous).
You can see this behavior if you add some logging:
var some_function() {
var outer_var;
console.log("before registration of callback"); //<----------
rest.get(url).on('complete', function(result, response) {
console.log("callback is called"); //<----------
if (result instanceof Error) {
sys.puts('Error: ' + result.message);
} else {
var inner_var;
// do stuff on **result** to build **inner_var**
outer_var = inner_var;
}
});
console.log("after registration of callback"); //<----------
return outer_var;
}
So if you would like to do something with this value you would need to call the function that should do something with this value out of your complete callback.
Remove the var outer_var; from your function some_function;
Declare the var outer_var; upper then your function
It should be work after you did step 1, not really need step 2.
I have something like this:
for (var i=0;i<result.qry.ROWCOUNT;i++) {
myAsync(i);
}
How do I know when all my Async functions have finished executing?
At the risk of someone replying with "Needs more jQuery!", can I use the jQuery promise object? Or deferred or something like that?
Keep track of how many asynchronous calls are outstanding. When each finishes, decrement your counter. When you get to 0, you are in the last callback.
var asyncsLeft = 0;
for (var i=0;i<10;++i){
asyncsLeft++;
doSomethingAsyncWithCallback(function(){
// This should be called when each asynchronous item is complete
if (--asyncsLeft==0){
// This is the last one!
}
});
}
Due to the single-threaded nature of JavaScript there is no potential race condition where you might get your callback invoked before all of the asynchronous calls have been queued up. It is safe to put the asyncsLeft++ call after the doSomethingAsynchronous, if you like.
This is how I would do it:
//Do stuff up here to get records
var rowCount = result.qry.ROWCOUNT, //Save the row count
asyncCount = 0, //The count of complete async calls
asyncCallback = function() {
//To be called whenever an async operation finishes
asyncCount++; //Increment the row counter
if (asyncCount >= rowCount) {
//Do stuff when they're all finished
}
};
for (var i=0;i<rowCount;i++) {
myAsync(i, asyncCallback);
}
function myAsync(index, completeCallback) {
//Do async stuff with index
//Call completeCallback when async stuff has finished or pass it
// into the async function to be called
}
In jQuery, there is the $.ajaxStop function that runs after the last Ajax has ran.
If you are using jQuery, you can also use the ajaxSend and ajaxComplete methods to keep your counter code separate from your dispatch code.
var ajaxPending = 0;
function ajax_changed(indicator, pending) {
if (pending)
$(indicator).show();
else
$(indicator).hide();
}
$('#loading-indicator').ajaxSend(function() {
ajax_changed(this, ++ajaxPending);
});
$('#loading-indicator').ajaxComplete(function() {
ajax_changed(this, --ajaxPending);
});
Use a callback function:
for (var i=0;i<result.qry.ROWCOUNT;i++) {
myAsync(i, myCallback);
}
function myCallback(i){
//set result.qry.ROWCOUNT to a var somewhere above if it's not available in this scope
if(i == (result.qry.ROWCOUNT - 1)){
//now you know you're actually done with all requests
}
}
function myAsync(i,callback){
///do work
callback(i);
}
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...