Let's say I have multiple functions func1, func2, func3, etc.....
And they all contain an AJAX/async function within them:
function funcX(){
// some ajax request
}
If in a main function I am calling func1, func2, func3 sequentially like so:
$(document).ready(function(){
func1();
func2();
func3();
...
}
Will each ajax/async function's call be certain to execute in the order of their parent functions? At first I thought they might be, but the behavior of my program seems to be suggesting otherwise...
If not, is there a good (hopefully simple?) alternative to having a long chain of callbacks?
Will each ajax/async function's call be certain to execute in the order of their parent functions?
They should execute in order, but their internal callbacks can be called in any order.
If not, is there a good (hopefully simple?) alternative to having a long chain of callbacks?
You could use a promise, and execute the next function when the promise has been resolved.
This example uses jQuery...
var fn1 = function () {
var d = $.Deferred();
setTimeout(function () {
$("body").text("Callback 1 done.") && d.resolve();
}, Math.random() * 1300 + 800);
return d.promise();
};
var fn2 = function () {
var d = $.Deferred();
setTimeout(function () {
$("body").text("Callback 2 done.") && d.resolve();
}, 500);
return d.promise();
};
$.when(fn1(), fn2()).then(function () {
setTimeout(function () {
$("body").text("All done.");
}, 300);
});
jsFiddle.
We use $.when() and pass the invoked functions we want to execute to it. We then use then() to show a final message (I placed a setTimeout() here so you can see the last resolved function's message in the document).
Each of these functions have their own deferred object which return the promise. A setTimeout() mocks an XHR for example's sake. When this callback is executed, we resolve the deferred object.
Once both have been deferred, we reach the callback for then().
To serialize tasks, I've written a helper function, which can also be found in my earlier answer:
function serializeTasks(arr, fn, done)
{
var current = 0;
fn(function iterate() {
if (++current < arr.length) {
fn(iterate, arr[current]);
} else {
done();
}
}, arr[current]);
}
It takes an array of values (in your case those are actually functions), a loop function and a completion handler. Below is the loop function:
function loopFn(nextTask, fn)
{
fn(nextTask);
}
It accepts an intermediate completion function as the first argument and each element of the aforementioned array.
To set everything in motion:
serializeTasks([func1, func2, func3], loopFn, function() {
console.log('all done');
});
Your functions are called with a single argument which should be passed to the AJAX success callback, e.g.
func1(nextTask)
{
$.ajax({
...,
success: nextTask
});
}
The order in which the asynch results are returned is not deterministic, and may wary every time.
func2 might complete before func1 etc
It is important to ensure correct order of execution. One pattern is to call the next function in the success callback of the prior function
Ex:
$.get("/someUrl",function(){
$.get("/nextAjaxCall", function(){
.....
});
});
If the dependency chain is very simple, I don't think it's necessary to introduce a framework to handle this
Or look at async library and it's awesomeness !
async
Related
I'm trying to work through this js/async scenario and i'm trying to know how the rest of the js world handles this.
function doStuff(callback) {
cursor.each(function(err, blahblah) {
...doing stuff here takes some time
});
... Execute this code ONLY after the `cursor.each` loop is finished
callback();
EDIT
Here's a more concrete example updated using most of the suggestions below which still doesn't work.
function doStuff(callback) {
MongoClient.connect(constants.mongoUrl, function(err, db) {
var collection = db.collection('cases2');
var cursor = collection.find();
var promises = []; // array for storing promises
cursor.each(function(err, item) {
console.log('inside each'); // NEVER GETS LOGGED UNLESS I COMMENT OUT THIS LINE: return Q.all(promises).then(callback(null, items));
var def = Q.defer(); // Create deferred object and store
promises.push(def.promise); // Its promise in the array
if(item == null) {
return def.resolve();
}
def.resolve(); // resolve the promise
});
console.log('items'); // ALWAYS GETS CALLED
console.log(items);
// IF I COMMENT THIS LINE OUT COMPLETELY,
// THE LOG STATEMENT INSIDE CURSOR.EACH ACTUALLY GETS LOGGED
return Q.all(promises).then(callback(null, items));
});
}
without using promises or any other dependencies/libraries you can simply
function doStuff(callback) {
add a counter
var cursor = new Array(); // init with some array data
var cursorTasks = cursor.length;
function cursorTaskComplete()
{
cursorTasks--;
if ( cursorTasks <= 0 ) {
// this gets get called after each task reported to be complete
callback();
}
}
for ( var i = 0; i < cursor.length; i++ ) {
...doing stuff here takes some time and does some async stuff
check after each async request
...when async operation is complete call
cursorTaskComplete()
}
}
Without knowing the details of the async calls you're making within the cursor.each loop, I shall assume that you have the ability to invoke a callback each time the functions invoked therein have completed their async task:
function doStuff() {
var promises = []; // array for storing promises
cursor.each(function(err, blahblah) {
var def = Q.defer(); // create deferred object and store
promises.push(def.promise); // its promise in the array
call_async_function(..., def.resolve); // resolve the promise in the async function's callback
});
// pass the array to Q.all, only when all are resolved will "callback" be called
return Q.all(promises);
}
and the usage then becomes:
doStuff().then(callback)
Note how the invocation of the callback now never touches the doStuff function - that function now also returns a promise. You can now register multiple callbacks, failure callbacks, etc, all without modifying doStuff. This is called "separation of concerns".
[NB: all the above based on the Q promises library - https://github.com/kriskowal/q]
EDIT further discussion and experimentation has determined that the .each call is itself async, and gives no indication to the outside when the last row has been seen. I've created a Gist that demonstrates a resolution to this problem.
if you want to do it with the async module, you can make use of the async forEachSeries function
Code snippet:
function doStuff(callback) {
async.forEachSeries(cursor, function(cursorSingleObj,callbackFromForEach){
//...do stuff which takes time
//this callback is to tell when everything gets over execute the next function
callbackFromForEach();
},function(){
//over here the execution of forEach gets over and then the main callback is called
callback();
});
}
In my mind an elegant/ideal solution would be to have something like
cursor.each(........).then( function() { ....your stuff});
But without that you can do this....UPDATED
http://plnkr.co/edit/27l7t5VLszBIW9eFW4Ip?p=preview
The gist of this is as shown below...notice....when
var doStuff = function(callback) {
cursor.forEach(function(cursorStep) {
var deferred = $q.defer();
var promise = deferred.promise;
allMyAsyncPromises.push(promise);
cursorStep.execFn(cursorStep.stepMeta);
promise.resolve;
});
$q.when(allMyAsyncPromises).then(callback);
}
After hitting the start button wait for few seconds...the async tasks have been simulated to finish in 5 seconds so the status will update accordingly.
Not having access to a real cursor object..I had to resort of fake cursor like and array.
I have a JavaScript function like the following.
function changeTheDom(var1, var2, var3) {
// Use DWR to get some server information
// In the DWR callback, add a element to DOM
}
This function is called in a couple of places in the page. Sometimes, in a loop. It's important that the elements be added to the DOM in the order that the changeTheDom function is called.
I originally tried adding DWREngine.setAsync(false); to the beginning of my function and DWREngine.setAsync(true); to the end of my function. While this worked, it was causing utter craziness on the rest of the page.
So I am wondering if there is a way to lock the changeTheDom function. I found this post but I couldn't really follow the else loop or how the lockingFunction was intended to be called.
Any help understanding that post or just making a locking procedure would be appreciated.
Don't try to lock anything. The cleanest way is always to adapt to the asynchronous nature of your code. So if you have an asynchronous function, use a callback. In your particular case I would suggest that you split your function up in one part that is executed before the asych call and one part that is executed afterwards:
function changeTheDomBefore(var1, var2, var3) {
//some code
//...
asyncFunction(function(result){
//this will be executed when the asynchronous function is done
changeTheDomAfter(var1, var2, var2, result);
});
}
function changeTheDomAfter(var1, var2, var3, asynchResult) {
//more code
//...
}
asyncFunction is the asynchronous function which, in this example, takes one argument - the callback function, which then calls your second changeTheDom function.
I think I finally got what you mean and I decided to create another answer, which is hopefully more helpful.
To preserve order when dealing with multiple asynchronous function calls, you could write a simple Queue class:
function Queue(){
var queue = [];
this.add = function(func, data) {
queue.push({func:func,data:data});
if (queue.length === 1) {
go();
}
};
function go() {
if (queue.length > 0) {
var func = queue[0].func,
data = queue[0].data;
//example of an async call with callback
async(function() {
func.apply(this, arguments);
queue.shift();
go();
});
}
}
};
var queue = new Queue();
function doit(data){
queue.add(function(result){
console.log(result);
}, data);
}
for (var i = 0; i < 10; i++) {
doit({
json: JSON.stringify({
index: i
}),
delay: 1 - i / 10.0
});
}
FIDDLE
So everytime you invoke your async function, you call queue.add() which adds your function in the queue and ensures that it will only execute when everything else in the queue is finished.
I want to say when this function close() is finished run this function init(). But it's not working for me.
$.when(close(toolTip)).done(init(toolTip, anchor));
I am not using the $.when for anything ajax related, just trying to make sure close() is finished before I call init(), and no I can't stick init() at the end of close(). Any ideas?
ok here is close()
var close = function (toolTip) {
toolTip.fadeOut('fast', function (e) {
if (typeof e !== 'undefined') {
//Re-set values applied when initted
var toolTipBd = toolTip.find('.bd:first');
toolTip.css('width', '');
toolTipBd.css('max-height', '');
toolTip.css('max-height', '');
toolTipBd.css('overflowY', '');
}
});
};
No where in close() can it call init().
Your close() implementation should be like this:
var close = function (toolTip) {
var d = $.Deferred();
toolTip.fadeOut('fast', function (e) {
if (typeof e !== 'undefined') {
//Re-set values applied when initted
var toolTipBd = toolTip.find('.bd:first');
toolTip.css('width', '');
toolTipBd.css('max-height', '');
toolTip.css('max-height', '');
toolTipBd.css('overflowY', '');
}
d.resolve();
});
return d.promise();
};
$.when works with Deferred's. It returns a new Deferred which will resolve when all the Deferred's you provided resolve.
As close() doesn't seem to be returning a Promise, when will resolve straight away (per the docs for when().
However, if close() is synchronous, you don't need when() at all. If it is asynchronous, you need to return a Promise, and resolve it when your animation or whatever has completed;
function close(what) {
var promise = jQuery.Deferred();
what.fadeOut('slow', function () {
promise.resolve();
});
return promise.promise();
}
... but you still don't need $.when as only 1 promise is involved. $.when is only useful when multiple promises are at play.
close(toolTip).done(function () {
init(toolTip, anchor);
});
Note also that done(init(tooltip, anchor)) will call init immediately, and pass the result of that function invocation to done(); instead, you need to pass a function to done. As init needs parameters, we've fixed this by introducing an anonymous function. If init didn't need any parameters, it'd have been as simple as:
close(toolTip).done(init);
Simply return toolTip:
return toolTip.fadeOut(...
using the callback to resolve a deferred object can result in odd results if there are more than one elements selected for whatever reason.
This works because jQuery objects have a .promise method that when called, return a promise object that resolves when all active animations are completed. $.when calls .promise on all passed in arguments.
You'll also need to call init differently, for example,
$.when(close(toolTip)).done(function(){
init(toolTip, anchor);
});
And, as pointed out by others, you could then shorten that to
close(toolTip).promise().done(function(){
init(toolTip, anchor);
});
I'm having an issue with normal (non-ajax) functions that involve lots of animations within each of them. Currently I simply have a setTimeout between functions, but this isn't perfect since no browsers / computers are the same.
Additional Note: They both have separate animations/etc that collide.
I can't simply put one in the callback function of another
// multiple dom animations / etc
FunctionOne();
// What I -was- doing to wait till running the next function filled
// with animations, etc
setTimeout(function () {
FunctionTwo(); // other dom animations (some triggering on previous ones)
}, 1000);
Is there anyway in js/jQuery to have:
// Pseudo-code
-do FunctionOne()
-when finished :: run -> FunctionTwo()
I know about $.when() & $.done(), but those are for AJAX...
MY UPDATED SOLUTION
jQuery has an exposed variable (that for some reason isn't listed anywhere in the jQuery docs) called $.timers, which holds the array of animations currently taking place.
function animationsTest (callback) {
// Test if ANY/ALL page animations are currently active
var testAnimationInterval = setInterval(function () {
if (! $.timers.length) { // any page animations finished
clearInterval(testAnimationInterval);
callback();
}
}, 25);
};
Basic useage:
// run some function with animations etc
functionWithAnimations();
animationsTest(function () { // <-- this will run once all the above animations are finished
// your callback (things to do after all animations are done)
runNextAnimations();
});
You can use jQuery's $.Deferred
var FunctionOne = function () {
// create a deferred object
var r = $.Deferred();
// do whatever you want (e.g. ajax/animations other asyc tasks)
setTimeout(function () {
// and call `resolve` on the deferred object, once you're done
r.resolve();
}, 2500);
// return the deferred object
return r;
};
// define FunctionTwo as needed
var FunctionTwo = function () {
console.log('FunctionTwo');
};
// call FunctionOne and use the `done` method
// with `FunctionTwo` as it's parameter
FunctionOne().done(FunctionTwo);
you could also pack multiple deferreds together:
var FunctionOne = function () {
var
a = $.Deferred(),
b = $.Deferred();
// some fake asyc task
setTimeout(function () {
console.log('a done');
a.resolve();
}, Math.random() * 4000);
// some other fake asyc task
setTimeout(function () {
console.log('b done');
b.resolve();
}, Math.random() * 4000);
return $.Deferred(function (def) {
$.when(a, b).done(function () {
def.resolve();
});
});
};
http://jsfiddle.net/p22dK/
add the following to the end of the first function
return $.Deferred().resolve();
call both functions like so
functionOne().done(functionTwo);
Along with Yoshi's answer, I have found another very simple (callback type) solution for animations.
jQuery has an exposed variable (that for some reason isn't listed anywhere in the jQuery docs) called $.timers, which holds the array of animations currently taking place.
function animationsTest (callback) {
// Test if ANY/ALL page animations are currently active
var testAnimationInterval = setInterval(function () {
if (! $.timers.length) { // any page animations finished
clearInterval(testAnimationInterval);
callback();
}
}, 25);
};
Basic useage:
functionOne(); // one with animations
animationsTest(functionTwo);
Hope this helps some people out!
This answer uses promises, a JavaScript feature of the ECMAScript 6 standard. If your target platform does not support promises, polyfill it with PromiseJs.
You can get the Deferred object jQuery creates for the animation using .promise() on the animation call. Wrapping these Deferreds into ES6 Promises results in much cleaner code than using timers.
You can also use Deferreds directly, but this is generally discouraged because they do not follow the Promises/A+ specification.
The resulting code would look like this:
var p1 = Promise.resolve($('#Content').animate({ opacity: 0.5 }, { duration: 500, queue: false }).promise());
var p2 = Promise.resolve($('#Content').animate({ marginLeft: "-100px" }, { duration: 2000, queue: false }).promise());
Promise.all([p1, p2]).then(function () {
return $('#Content').animate({ width: 0 }, { duration: 500, queue: false }).promise();
});
Note that the function in Promise.all() returns the promise. This is where magic happens. If in a then call a promise is returned, the next then call will wait for that promise to be resolved before executing.
jQuery uses an animation queue for each element. So animations on the same element are executed synchronously. In this case you wouldn't have to use promises at all!
I have disabled the jQuery animation queue to demonstrate how it would work with promises.
Promise.all() takes an array of promises and creates a new Promise that finishes after all promises in the array finished.
Promise.race() also takes an array of promises, but finishes as soon as the first Promise finished.
Is this what you mean man: http://jsfiddle.net/LF75a/
You will have one function fire the next function and so on, i.e. add another function call and then add your functionONe at the bottom of it.
Please lemme know if I missed anything, hope it fits the cause :)
or this: Call a function after previous function is complete
Code:
function hulk()
{
// do some stuff...
}
function simpsons()
{
// do some stuff...
hulk();
}
function thor()
{
// do some stuff...
simpsons();
}
ECMAScript 6 UPDATE
This uses a new feature of JavaScript called Promises
functionOne().then(functionTwo);
You can do it via callback function.
$('a.button').click(function(){
if (condition == 'true'){
function1(someVariable, function() {
function2(someOtherVariable);
});
}
else {
doThis(someVariable);
}
});
function function1(param, callback) {
...do stuff
callback();
}
Here is a solution for n-calls (recursive function).
https://jsfiddle.net/mathew11/5f3mu0f4/7/
function myFunction(array){
var r = $.Deferred();
if(array.length == 0){
r.resolve();
return r;
}
var element = array.shift();
// async task
timer = setTimeout(function(){
$("a").text($("a").text()+ " " + element);
var resolving = function(){
r.resolve();
}
myFunction(array).done(resolving);
}, 500);
return r;
}
//Starting the function
var myArray = ["Hi", "that's", "just", "a", "test"];
var alerting = function (){window.alert("finished!")};
myFunction(myArray).done(alerting);
You can use the javascript Promise and async/await to implement a synchronized call of the functions.
Suppose you want to execute n number of functions in a synchronized manner that are stored in an array, here is my solution for that.
async function executeActionQueue(funArray) {
var length = funArray.length;
for(var i = 0; i < length; i++) {
await executeFun(funArray[i]);
}
};
function executeFun(fun) {
return new Promise((resolve, reject) => {
// Execute required function here
fun()
.then((data) => {
// do required with data
resolve(true);
})
.catch((error) => {
// handle error
resolve(true);
});
})
};
executeActionQueue(funArray);
I'm using jquery to call some javascript functions with a delay between them.
Also I'm using Jquery Wait
When I call below function,all functions are called recpectively,there are no delays between each other.
$(this)
.call(f1)
.wait(5000)
.call(f2)
.wait(5000)
.call(f3);
Here call function calls some function as I did
$.fn.call = function (f) {
if (f)
f();
return this;
};
What am i doing wrong ?
How can i achieve something like this ?
Thank you
If you want to call a function every 5 seconds use
setTimeout(function(){f1},5000);
setTimeout(function(){f2},10000);
setTimeout(function(){f2},15000);
if you want to call each function 5 seconds after the last one terminated use
setTimeout(function(){f1;setTimeout(function(){f2;setTimeout(function(){f3},5000);},5000);},5000);
You don't need wait() from that cookbook; delay() is built-in and appears to have the same functionality. But either function involves adding something to jQuery's internal queue of effects and then removing it after a timeout expires, i.e. it's not a sleep statement, so it's not going to wait around before returning.
If you want to use delay() or wait(), you should make call() enqueue the function with queue(). Just sketching, but something like:
$.fn.call = function(f) {
if (f) {
$(this).queue(function() {
f();
$(this).dequeue();
}
}
return this;
}
Then I'd expect your code to work the way you intend.
Here is a function that calls in sequence an array of function:
$.fn.callFn = function(fns, delay) {
var fn, that = this;
if(fns.length > 0){
fn = fns.shift()
fn && fn();
setTimeout(function(){
that.callFn(fns, delay);
}, delay);
}
return this;
};
And you would call it like that:
$(this).callFn([f1, f2, f3], 2000);
$('#box').slideUp(300).delay(800).fadeIn(400);
/* .delay = wait time = 800 (this means it will wait 800/1000 of a second/ "1000 = 1 second") */