jQuery: Making a Plugin Wait Until Existing Calls Are Done To Execute? - javascript

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/

Related

jQuery: using deferred properly in a local context (ie. no AJAX)

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

Using getScript synchronously

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

Is it possible to use a Deferred object in order to create a callback after a for loop has completed?

I have a for loop which fades in each li, what I want to do is wait until the very last li has fully faded in and then continue with the code similar to a callback but I'm unsure how to achieve this? I was thinking that I could possibly use a Deferred object?
JS
var newArray = [3,2,6,4,0,1,5];
for (var i = 0; i < newArray.length; i++) {
var dfd = $.Deferred();
$(this).eq(newArray[i]).fadeIn(i * 500);
dfd.resolve();
//.. continue with callback code??
}
You can use $.when: by passing the Deferreds from all your fadeIn calls to it, you can register a callback to be executed only when all of them are done:
var deferreds = [];
for (var i = 0; i < newArray.length; i++) {
var dfd = $(this).eq(newArray[i]).fadeIn(i * 500);
deferreds.push(dfd);
}
$.when.apply($, deferreds).then(function() { ... });
Working example at jsFiddle. Note that you can use the return value of fadeIn as a Deferred.
Update: since you want each fadeIn to start only after the last one ended, Bergi's answer might be more appropriate. An alternative (simpler, IMHO) could be:
var i = 0;
function f() {
if ( i < newArray.length ) {
$(this).eq(newArray[i]).fadeIn(i * 500, f);
i++;
} else {
// Your "done" callback, if any
}
}
f();
Working example. I stuck to your original code (each effect using a different duration), but if you want all them to have the same one, remove the i * and just use 500.
I don't think Deferreds will be of great help here. Surely, you can get a .promise() for every [effect] queue on jQuery instances, and because of that method you could even pass jQuery objects right into $.when, but I think a callback chain - and for successive animations you need some chain - can do it easier:
function chainedFadeIn($el, order, callback) {
if (!order.length)
return callback();
$el.eq(order.shift()).fadeIn(500, function() {
chainedFadeIn($el, order, callback); // notice we removed the first element
});
}
chainedFadeIn($(this), [3,2,6,4,0,1,5], function() {
// do something
});
Alternative version with Promises:
function getFadeInChain($el, order) {
if (!order.length)
return order; // or anything else
return $el
.eq(order.shift())
.fadeIn(500)
.promise()
.then(getFadeInChain.bind(null, $el, order));
}
getFadeInChain($(this), [3,2,6,4,0,1,5]).done(function() {
// do something
});
Demos at jsfiddle.net: callbacks, Deferred

Wait till a Function with animations is finished until running another 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);

Knowing when all other ready callbacks are done

I want my handler of the ready event will fire after all other handlers are done.
It's extremely handy for manipulating plugins' undesired actions.
If I write my handler after all others, it only guarantees it will fire after all others fired, not finished:
$(function() {
setTimeout(function() { alert('other handler'); }, 500);
});
$(function() { alert('my handler'); });​
Fiddle
In that code, my handler alerted first.
I read that before jQuery version 1.4 the readyList was public. so in version 1.7 I have no idea how I can tell that my handler is the last handler or not.
If the idea is that you don't control the other ready handlers, then given your example where another handler used a setTimeout, you can never actually know (without inspecting the other code) if your code will run after all other code.
The readyList wouldn't help even if it was public, because in your example, the handler with the setTimeout will be removed from the readyList long before the setTimeout handler runs. The readyList Array doesn't have any control over that sort of asynchronous code either.
So if you don't control (can't modify) the other code, then I really don't have a solution. But if the other code is just long running, but not asynchronous, then there wouldn't be any issue, because if your code is the last .ready() handler assigned, it shouldn't matter how long the other handlers take to execute. If their code is synchronous, it will force yours to wait until they're complete. It's just that if they're using asynchronous code, like your setTimeout example, then there's nothing you can do short of examining the other code, and modifying yours to make sure it fires last.
You can use something like this:
function Join(cb) {
var paths = 0;
var triggerCallback = cb;
this.add = function () {
paths ++;
return this.call;
};
this.call = function () {
paths --;
if (paths == 0)
if (triggerCallback)
triggerCallback();
};
return this;
}
An example:
function finishedAll() {
alert("All finished");
}
window.join = new Join(finishedAll);
function sampleCall(callJoinHandle) {
alert("Not done yet.");
if (callJoinHandle) callJoinHandle();
}
var cb1 = join.add();
setTimeout(function () { sampleCall(cb1); }, 1000);
var cb2 = join.add();
setTimeout(function () { sampleCall(cb2); }, 1000);
var cb3 = join.add();
setTimeout(function () { sampleCall(cb3); }, 1000);
An idea could be creating an array of deferred to use inside every ready function (except the last one), resolving each one when the snippet has completed.
Then, in the last ready function you could simply check the promise resolution with $.when and then execute some other code: e.g.
var dfdArray = [];
$(function() {
var dfd = $.Deferred();
dfdArray.push(dfd);
setTimeout(function() {
console.log('another simple handler');
dfd.resolve();
}, 2000);
});
$(function() {
var dfd = $.Deferred();
dfdArray.push(dfd);
setTimeout(function() {
console.log('first handler');
dfd.resolve();
}, 1200);
});
$(function() {
$.when.apply($, dfdArray).done(function() {
alert('my final handler');
})
});
See fiddle in action here: http://jsfiddle.net/DXaw5/
I don't know if it is possible for you to create a queue for all the functions like
var queue = [];
queue .push(fun1);
queue .push(fun2);
//execute the first function and remove it.
(queue .shift())();
I usually use the following pattern, simply keepig a counter of finished async functions:
var fired = 10;
var finished = 0;
for (var i = 0; i < fired; i++) {
// Call an asynchronous function 10 times
async_function(function() {
// When asynchronous function finishes,
// we check if it was the last one.
if (++finished == fired) all_ready();
});
}
The same in coffeescript:
fired = 10
finished = 0
(async_function -> all_ready() if ++finished == ready) for n in [0...fired]
(We call the same function for 10 times to keep the example simple, while in reality you may of course call different functions, but the same idea apply; in callback function you check the counter.)

Categories