Apologies for what I'm sure is a repost; I really have looked quite widely for an answer to my question (that I also understood).
What I'm trying to learn to do is to arbitrarily chain functions such that they must complete before the next occurs, which, as I understand it, is the purpose of jQuery's deferred(). So in the below code, what I'm imagining should happen is:
the function contained within the load deferred objects executes; after which
the function contained in then() executes; after which
the function contained in done() executes.
Every tutorial in the universe uses a $.ajax() object after $.when(), which is useless if all one wants is control of execution sequence in a local context.
Here's what I've been trying:
var preloadDone = false,
var testDone = false,
var load = $.deferred(function() {
//cacheImages() is a plugin, works fine
$("img.image-loader.preload").cacheImages();
preloadDone = true;
});
var loader = $.when(load)
.then(function() {
if (preloadDone) {
console.log("then called in sequence");
} else {
console.log("then called out of sequence"); // wrong order, every time
}
XBS.cache.cbDone = true;
}).done(function() {
if (XBS.cache.cbDone) {
console.log("even done was called in right sequence!"); // proper order, every time
} else {
console.log("done() wasn't called in order..");
}
});
load.resolve(); // nothing happens
// load(); also tried this; nothing happens
So far as I can tell, this is identical to the example given in the jQuery $.when() documentation. Lil help?
Here is a demo on how to run many functions one after another but only after each funtion has completed. This is achieved by using an Async function.
Demo (Runs 3 functions one after the other. Where i have alert("starting *") that is were you want to put the work you like to do and in the done function you include the next function you want to run. )
http://jsfiddle.net/5xLbk91c/
//the Assync function. Pause is the time in miliseconds to pause between loops
var asyncFor = function(params) {
var defaults = {
total: 0,
limit: 1,
pause: 10,
context: this
},
options = $.extend(defaults, params),
def = $.Deferred(),
step = 0,
done = 0;
this.loop = function() {
if (done < options.total) {
step = 0;
for (; step < options.limit; step += 1, done += 1) {
def.notifyWith(options.context, [done]);
}
setTimeout.apply(this, [this.loop, options.pause]);
} else {
def.resolveWith(options.context);
}
};
setTimeout.apply(this, [this.loop, options.pause]);
return def;
};
function one() {
asyncFor({
total: 1, // run only once. If you want to loop then increase to desired total.
context: this
}).progress(function(step) {
alert("starting one")
}).done(function() {
alert("finished one")
two()
});
}
function two() {
asyncFor({
total: 1,
context: this
}).progress(function(step) {
alert("starting two")
}).done(function() {
alert("finished two")
three()
});
}
function three() {
asyncFor({
total: 1,
context: this
}).progress(function(step) {
alert("starting three")
}).done(function() {
alert("finished three and all done")
});
}
you may want to start your investigations by this change to your code:
var load = function() {
var deferred = $.Deferred();
$("img.image-loader.preload").cacheImages();
preloadDone = true;
return deferred;
};
Please also note you may pass array of promises to $.when().
Best regards
Related
I should begin by saying that, while I'm pretty familiar with basic JavaScript/jQuery, I'm by no means an expert. So this could be a simple concept I'm not grasping. At any rate, I'll explain my situation as best I can.
I have been piecing together a jQuery plugin that takes a string and displays it one character at a time until the end of the string has been reached. Each call starts and stops on an interval as needed until done:
$.fn.writeText = function(content) {
var elem = this;
var contentArray = content.split("");
return this.each(function() {
var current = 0;
var length = contentArray.length;
intervalText();
function intervalText(){
var interval = setInterval(function(){
if(current < length){
//If end punctuation is detected [omitted for simplicity's sake]
{
elem.text(elem.text() + contentArray[current++],play('tes.wav'));
clearInterval(interval);
setTimeout(function(){intervalText();},500);
}
//Else if 'pause' punctuation is detected.
{
elem.text(elem.text() + contentArray[current++],play('tes.wav'));
clearInterval(interval);
setTimeout(function (){intervalText();},200);
}
else{
elem.text(elem.text() + contentArray[current++],play('tes.wav'));
}
}
else if(current == length+1){
clearInterval(interval);
}
}, 50);
}
});
}
One thing I've noticed is that using the plugin back to back causes problems:
$("#promptText").writeText("This test.");
$("#promptText").writeText("This is also a test.");
//Results in "TThhiiss tiess ta.lso a test."
I determined pretty quickly that the issue is just that characters are being added to the same element, and that the plugin isn't doing anything "wrong." But, as you can imagine I'd like to prevent this from happening.
In order to do that, I'd need to cause any secondary calls to the plugin to wait until the previous ones were finished. Any suggestions as to how I'd go about this?
This is a great application for jQuery's Deferred() objects. Deferreds let you start a function, then chain additional calls after that function completes (the $.ajax() function uses Deferreds internally).
In this case, you can queue up a new Deferred each time you call $().writeText(), and make them call your internal function in order:
var deferred;
$.fn.writeText = function (content) {
var d = $.Deferred(); //Each execution gets its own deferred
var elem = this;
var f = function () {
writeTextInternal(content, elem, d); //Do it
}
if (deferred) { //If there is already a deferred, queue the new request
deferred.then(f); //Calls f() when deferred is resolved, or immediately if already resolved
} else {
f(); //Call immediately, because there are no deferreds
}
deferred = d.promise(); //Save this deferred to chain subsequent calls.
return deferred; //Allows other functions to do something after this text is typed.
}
var writeTextInternal = function(content, elem, d) {
//Slightly modified original function that will resolve our deferred...
}
Working JSFiddle: http://jsfiddle.net/bt5tee3r/3/
I'm writing an engine that requires the use of getScript quite extensively. I've pushed it into its own function, for ease of use, but now I need to make sure that the function itself is synchronous. Unfortunately, I can't seem to make getScript wait until the script it loads is actually finished loading before proceeding. I've even tried setting jQuery's ajax asynch property to false before making the call. I'm thinking of using jQuery's when/done protocol, but I can't seem to wrap my head around the logic of placing it inside a function and making the function itself synchronous. Any help would be very much appreciated!
function loadScript(script){
//Unrelated stuff here!!!
$.when(
$.getScript(script,function(){
//Unrelated stuff here!!!
})).done(function(){
//Wait until done, then finish function
});
}
Loop code (by request):
for (var i in divlist){
switch($("#"+divlist[i]).css({"background-color"})){
case #FFF:
loadScript(scriptlist[0],divlist[i]);
break;
case #000:
loadScript(scriptlist[2],divlist[i]);
break;
case #333:
loadScript(scriptlist[3],divlist[i]);
break;
case #777:
loadScript(scriptlist[4],divlist[i]);
break;
}
}
This worked for me, and may help you.
$.ajax({
async: false,
url: "jui/js/jquery-ui-1.8.20.min.js",
dataType: "script"
});
Basically, I just bypassed the shorthand notation and added in the async: false
As I said, it's relatively easy to chain Ajax calls with promise objects. Now, it don't see why the scripts have to be loaded one after the other, but you will have a reason for it.
First though I would get rid of the switch statement if you are only calling the same function with different arguments. E.g. you can put all the script URLs in a map:
var scripts = {
'#FFF': '...',
'#000': '...'
// etc.
};
You can chain promises by simply returning another promise from a callback passed to .then [docs]. All you need to do is start with a promise or deferred object:
var deferred = new $.Deferred();
var promise = deferred.promise();
for (var i in divlist) {
// we need an immediately invoked function expression to capture
// the current value of the iteration
(function($element) {
// chaining the promises,
// by assigning the new promise to the variable
// and returning a promise from the callback
promise = promise.then(function() {
return loadScript(
scripts[$element.css("background-color")],
$element
);
});
}($('#' + divlist[i])));
}
promise.done(function() {
// optional: Do something after all scripts have been loaded
});
// Resolve the deferred object and trigger the callbacks
deferred.resolve();
In loadScript, you simply return the promise returned from $.getScript or the one returned by .done:
function loadScript(script_url, $element){
// Unrelated stuff here!!!
return $.getScript(script_url).done(function(){
// Unrelated stuff here
// do something with $element after the script loaded.
});
}
The scripts will all be called in the order the are access in the loop. Note that if divlist is an array, you really should use normal for loop instead of a for...in loop.
Do you know that $.getScript accepts a callback function that is called synchronously after the script is loaded?
Example:
$.getScript(url,function(){
//do after loading script
});
I have 2 more solutions: a pure js one and one for multiple js load.
Try this way, create array with deferred objects and used $.when with "apply"
var scripts = [
'src/script1.js',
'src/script2.js'
];
var queue = scripts.map(function(script) {
return $.getScript(script);
});
$.when.apply(null, queue).done(function() {
// Wait until done, then finish function
});
var getScript = function(url) {
var s = document.createElement('script');
s.async = true;
s.src = url;
var to = document.getElementsByTagName('script')[0];
to.parentNode.insertBefore(s, to);
};
#Felix Kling's answer was a great start. However, I discovered that there was a slight issue with the overall attached .done() at the end of the .getScripts() returned result if I wanted to "functionalize" it. You need the last promise from the chained .getScript() iterations from within the loop. Here's the modified version of his solution (thank you, BTW).
Plugin:
(function ($) {
var fetched = new function () {
this.scripts = [];
this.set = [];
this.exists = function (url) {
var exists = false;
$.each(this.set, function (index, value) {
if ((url || '') === value) {
exists = true;
return false;
}
});
return exists;
};
this.buildScriptList = function () {
var that = this;
that.set = [];
$('script').each(function () {
var src = $(this).attr('src') || false;
if (src) {
that.set.push(src);
}
});
$.merge(this.set, this.scripts);
return this;
};
},
getScript = $.getScript;
$.getScript = function () {
var url = arguments[0] || '';
if (fetched.buildScriptList().exists(url)) {
return $.Deferred().resolve();
}
return getScript
.apply($, arguments)
.done(function () {
fetched.scripts.push(url);
});
};
$.extend({
getScripts: function (urls, cache) {
if (typeof urls === 'undefined') {
throw new Error('Invalid URL(s) given.');
}
var deferred = $.Deferred(),
promise = deferred.promise(),
last = $.Deferred().resolve();
if (!$.isArray(urls)) {
urls = [urls];
}
$.each(urls, function (index) {
promise = promise.then(function () {
last = $.getScript(urls[index]);
return last;
});
});
if (Boolean(cache || false) && !Boolean($.ajaxSetup().cache || false)) {
$.ajaxSetup({cache: true});
promise.done(function () {
$.ajaxSetup({cache: false});
});
}
deferred.resolve();
return last;
}
});
})($);
You can ignore the fetched function (I implemented it to reduce potential redundant calls - which is why I hijacked .getScript()) and see where the variable last is set inside the .getScripts() method. It defaults to a resolved deferred object, so that if the urls array is empty, it's passed to the returned result to attach the outer .done() call to. Otherwise, it will inevitably be assigned the last promise object from the chained .getScript() calls and thus will ensure everything will remain synchronous from outside the function.
Returning the initially created deferred object will not work if you resolve it before returning it back to the invoker (which is what you're supposed to do per jQuery's official documentation).
Example:
function loadStuff(data) {
var version = {
'accounting': '1.2.3',
'vue': '1.2.3',
'vueChart': '1.2.3'
};
$.getScripts([
'https://cdnjs.cloudflare.com/ajax/libs/accounting.js/' + version.accounting + '/accounting.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/vue/' + version.vue + '/vue.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/vue-chartjs/' + version.vueChart + '/vue-chartjs.min.js'
], true)
.done(function () {
// do stuff
})
.fail(function () {
throw new Error('There was a problem loading dependencies.');
});
}
Just create a script node, set its src property to the JS you want to load then append it to the head:
var myScript = document.createElement('script');
myScript.src = "thesource.js";
document.head.appendChild(myScript);
this is what I do
function loadJsFile(filename) {
$.ajaxSetup({
cache: true
});
var dloadJs = new $.Deferred();
$.when(dloadJs).done(function () {
$.ajaxSetup({
cache: false
});
});
dloadJs.resolve(
$.getScript(filename, function () { })
);
}
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);
Check out this code :
Link
<span>Moving</span>
$('#link').click(function () {
console.log("Enter");
$('#link').animate({ width: 200 }, 2000, function() {
console.log("finished");
});
console.log("Exit");
});
As you can see in the console, the "animate" function is asynchronous, and it "fork"s the flow of the event handler block code. In fact :
$('#link').click(function () {
console.log("Enter");
asyncFunct();
console.log("Exit");
});
function asyncFunct() {
console.log("finished");
}
follow the flow of the block code!
If I wish to create my function asyncFunct() { } with this behaviour, how can I do it with javascript/jquery? I think there is a strategy without the use of setTimeout()
You cannot make a truly custom asynchronous function. You'll eventually have to leverage on a technology provided natively, such as:
setInterval
setTimeout
requestAnimationFrame
XMLHttpRequest
WebSocket
Worker
Some HTML5 APIs such as the File API, Web Database API
Technologies that support onload
... many others
In fact, for the animation jQuery uses setInterval.
You can use a timer:
setTimeout( yourFn, 0 );
(where yourFn is a reference to your function)
or, with Lodash:
_.defer( yourFn );
Defers invoking the func until the current call stack has cleared. Any additional arguments are provided to func when it's invoked.
here you have simple solution (other write about it)
http://www.benlesh.com/2012/05/calling-javascript-function.html
And here you have above ready solution:
function async(your_function, callback) {
setTimeout(function() {
your_function();
if (callback) {callback();}
}, 0);
}
TEST 1 (may output '1 x 2 3' or '1 2 x 3' or '1 2 3 x'):
console.log(1);
async(function() {console.log('x')}, null);
console.log(2);
console.log(3);
TEST 2 (will always output 'x 1'):
async(function() {console.log('x');}, function() {console.log(1);});
This function is executed with timeout 0 - it will simulate asynchronous task
Here is a function that takes in another function and outputs a version that runs async.
var async = function (func) {
return function () {
var args = arguments;
setTimeout(function () {
func.apply(this, args);
}, 0);
};
};
It is used as a simple way to make an async function:
var anyncFunction = async(function (callback) {
doSomething();
callback();
});
This is different from #fider's answer because the function itself has its own structure (no callback added on, it's already in the function) and also because it creates a new function that can be used.
Edit: I totally misunderstood the question. In the browser, I would use setTimeout. If it was important that it ran in another thread, I would use Web Workers.
Late, but to show an easy solution using promises after their introduction in ES6, it handles asynchronous calls a lot easier:
You set the asynchronous code in a new promise:
var asyncFunct = new Promise(function(resolve, reject) {
$('#link').animate({ width: 200 }, 2000, function() {
console.log("finished");
resolve();
});
});
Note to set resolve() when async call finishes.
Then you add the code that you want to run after async call finishes inside .then() of the promise:
asyncFunct.then((result) => {
console.log("Exit");
});
Here is a snippet of it:
$('#link').click(function () {
console.log("Enter");
var asyncFunct = new Promise(function(resolve, reject) {
$('#link').animate({ width: 200 }, 2000, function() {
console.log("finished");
resolve();
});
});
asyncFunct.then((result) => {
console.log("Exit");
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Link
<span>Moving</span>
or JSFiddle
This page walks you through the basics of creating an async javascript function.
Since ES2017, asynchronous javacript functions are much easier to write. You should also read more on Promises.
If you want to use Parameters and regulate the maximum number of async functions you can use a simple async worker I've build:
var BackgroundWorker = function(maxTasks) {
this.maxTasks = maxTasks || 100;
this.runningTasks = 0;
this.taskQueue = [];
};
/* runs an async task */
BackgroundWorker.prototype.runTask = function(task, delay, params) {
var self = this;
if(self.runningTasks >= self.maxTasks) {
self.taskQueue.push({ task: task, delay: delay, params: params});
} else {
self.runningTasks += 1;
var runnable = function(params) {
try {
task(params);
} catch(err) {
console.log(err);
}
self.taskCompleted();
}
// this approach uses current standards:
setTimeout(runnable, delay, params);
}
}
BackgroundWorker.prototype.taskCompleted = function() {
this.runningTasks -= 1;
// are any tasks waiting in queue?
if(this.taskQueue.length > 0) {
// it seems so! let's run it x)
var taskInfo = this.taskQueue.splice(0, 1)[0];
this.runTask(taskInfo.task, taskInfo.delay, taskInfo.params);
}
}
You can use it like this:
var myFunction = function() {
...
}
var myFunctionB = function() {
...
}
var myParams = { name: "John" };
var bgworker = new BackgroundWorker();
bgworker.runTask(myFunction, 0, myParams);
bgworker.runTask(myFunctionB, 0, null);
Function.prototype.applyAsync = function(params, cb){
var function_context = this;
setTimeout(function(){
var val = function_context.apply(undefined, params);
if(cb) cb(val);
}, 0);
}
// usage
var double = function(n){return 2*n;};
var display = function(){console.log(arguments); return undefined;};
double.applyAsync([3], display);
Although not fundamentally different than the other solutions, I think my solution does a few additional nice things:
it allows for parameters to the functions
it passes the output of the function to the callback
it is added to Function.prototype allowing a nicer way to call it
Also, the similarity to the built-in function Function.prototype.apply seems appropriate to me.
Next to the great answer by #pimvdb, and just in case you where wondering, async.js does not offer truly asynchronous functions either. Here is a (very) stripped down version of the library's main method:
function asyncify(func) { // signature: func(array)
return function (array, callback) {
var result;
try {
result = func.apply(this, array);
} catch (e) {
return callback(e);
}
/* code ommited in case func returns a promise */
callback(null, result);
};
}
So the function protects from errors and gracefully hands it to the callback to handle, but the code is as synchronous as any other JS function.
Unfortunately, JavaScript doesn't provide an async functionality. It works only in a single one thread. But the most of the modern browsers provide Workers, that are second scripts which gets executed in background and can return a result.
So, I reached a solution I think it's useful to asynchronously run a function, which creates a worker for each async call.
The code below contains the function async to call in background.
Function.prototype.async = function(callback) {
let blob = new Blob([ "self.addEventListener('message', function(e) { self.postMessage({ result: (" + this + ").apply(null, e.data) }); }, false);" ], { type: "text/javascript" });
let worker = new Worker(window.URL.createObjectURL(blob));
worker.addEventListener("message", function(e) {
this(e.data.result);
}.bind(callback), false);
return function() {
this.postMessage(Array.from(arguments));
}.bind(worker);
};
This is an example for usage:
(function(x) {
for (let i = 0; i < 999999999; i++) {}
return x * 2;
}).async(function(result) {
alert(result);
})(10);
This executes a function which iterate a for with a huge number to take time as demonstration of asynchronicity, and then gets the double of the passed number.
The async method provides a function which calls the wanted function in background, and in that which is provided as parameter of async callbacks the return in its unique parameter.
So in the callback function I alert the result.
MDN has a good example on the use of setTimeout preserving "this".
Like the following:
function doSomething() {
// use 'this' to handle the selected element here
}
$(".someSelector").each(function() {
setTimeout(doSomething.bind(this), 0);
});
eg I have two concurrent AJAX requests, and I need the result from both to compute a third result. I'm using the Prototype library, so it might look something like this:
var r1 = new Ajax.Request(url1, ...);
var r2 = new Ajax.Request(url2, ...);
function on_both_requests_complete(resp1, resp2) {
...
}
One way would be to use polling, but I'm thinking there must be a better way.
Update: An acceptable solution must be free of race conditions.
On the callback function of each request, set a boolean such as
request1Complete and request2Complete
and call on_both_requests_complete(resp1,resp2).
In the handler function, check to see if both booleans are set. If not, just return and fall out of the function. The callback functions should be serialized, in that they cannot happen simultaneously, so this should work. If they could happen in parallel, you would break on a race condition.
This is how I would do it. The approach is a general one, which gives you more flexibility and reuse, and avoids coupling and the use of globals.
var makeEventHandler = function(eventMinimum, callback) {
var data = [];
var eventCount = 0;
var eventIndex = -1;
return function() {
// Create a local copy to avoid issues with closure in the inner-most function
var ei = ++eventIndex;
return function() {
// Convert arguments into an array
data[ei] = Array.prototype.slice.call(arguments);
// If the minimum event count has not be reached, return
if ( ++eventCount < eventMinimum ) {
return;
}
// The minimum event count has been reached, execute the original callback
callback(data);
};
};
};
General usage:
// Make a multiple event handler that will wait for 3 events
var multipleEventHandler = makeMultipleEventHandler(3, function(data) {
// This is the callback that gets called after the third event
console.log(data);
});
multipleEventHandler()(1,2,3);
var t = multipleEventHandler();
setTimeout(function() {t("some string");}, 1000);
multipleEventHandler()({a: 4, b: 5, c: 6});
Output from callback (condensed by Firebug):
[[1, 2, 3], ["some string"], [Object { a=4, more...}]]
Notice that the order of data in the final callback is in order of the calling events, even though the second "event" executes after the third.
To use this in context of your Ajax requests:
var onBothComplete = makeMultipleEventHandler(2, function(data) {
// Do something
...
});
new Ajax.Request(url1, {onComplete: onBothComplete()});
new Ajax.Request(url2, {onComplete: onBothComplete()});
Edit: I've updated the function to force data to always maintain the asynchronously received event data in the synchronously executed order (the previous caveat no longer exists).
Well, you have to remember that the JS implementation in browsers is not really concurrent, and use that to your advantage. So what you would want to do is in each handler check if the other has finished. Example in jQuery:
var other_done = false;
$.get('/one', function() {
if (other_done) both_completed();
other_done = true;
alert('One!');
});
$.get('/two', function() {
if (other_done) both_completed();
other_done = true;
alert('Two!');
});
function both_completed() {
alert('Both!');
}
Based on Justin Johnson's response to this question:
function sync(delays /* Array of Functions */, on_complete /* Function */) {
var complete_count = 0;
var results = new Array(delays.length);
delays.length.times(function (i) {
function on_progress(result) {
results[i] = result;
if (++complete_count == delays.length) {
on_complete(results);
}
}
delays[i](on_progress);
});
}
This assumes each delay accepts one argument: an "on progress" event handler, which takes one argument: the result that the delay is trying to compute. To complete the example in my original question, you'd use it like so:
var delays = [];
delays[0] = function (on_progress) {
new Ajax.Request(url1, {onSuccess: on_progress});
};
delays[1] = function (on_progress) {
new Ajax.Request(url2, {onSuccess: on_progress});
};
function on_complete(results) { alert(results.inspect()); }
sync(delays, on_complete);
The one thing I'm not sure of is whether this avoids a race condition. If the expression ++complete_count == delays.length always happens atomically, then this should work.
You can use the concept where you set temporary variables and wait for the "last" request to go. To do this, you can have the two handle functions set the tmp vars to the return val and then call your "on_both_requests_complete" function.
var processed = false;
var r1 = new Ajax.Request(...);
var r2 = new Ajax.Request(...);
(function() {
var data1;
var data2;
function handle_r1(data) {
data1 = data;
on_both_requests_complete();
};
function handle_r2(data) {
data2 = data;
on_both_requests_complete();
};
function on_both_requests_complete() {
if ( (!data1 || !data2) || processed) {
return;
}
processed = true;
/* do something */
};
}();