Is there a way to delay the calling of a JavaScript function until two criteria are met?
I have a slideUp() animation and a .get() function that start at the same time, but either one could finish first. I want a function to be called when both have completed.
You just have to keep track, either a counter or (my preferred approach) flags for all relevant information.
var slideDone, getDone;
$("#foo").slideUp(function() {
slideDone = true;
nextThing();
});
$.get({
// ...
success: function() {
getDone = true;
nextThing();
}
});
function nextThing() {
if (slideDone && getDone) {
// Do the next thing
}
}
Now, obviously my two conditions above are very artificial, but I find that most of the time when this comes up in my real work, I have actual information I can use for the check and so don't resort to artificial conditions.
Or a counter:
var counter = 0;
++counter;
$("#foo").slideUp(function() {
nextThing();
});
++counter;
$.get({
// ...
success: function() {
nextThing();
}
});
function nextThing() {
if (--counter === 0) {
// Do the next thing
}
}
To someone used to multi-threaded programming, that looks like a race condition (what if the slideUp completes before we start the get?), but it's not one in JavaScript on browsers, which is single-threaded (barring the use of web workers, which use a syntax that wouldn't apply to the above anyway).
If you find this coming up a lot, you could of course always isolate the logic (not that there's a lot of it, but...) into an object and reuse it.
You could create a simple boolean variable which both delegate methods have acesss to and check whether the previous method has executed.
This way you can check in your delegate functions if the previous function has completed and execute some method.
T.J. Crowders answer includes some good example code of this approach.
You can use jQuery's Deferred Object:
http://api.jquery.com/category/deferred-object/
It's actually perfect for what you need. You can set a function to only execute when all the deferred objects bound to it get resolved. Some of jQuery's native functions return a deferred object by default such as the $.ajax and I believe animations as well (the latest version of jQuery at least)
Read the docs, it'll do a much better job at explaining it than I could.
Related
I used the code below for years when I want to open a window and check when an specific element becomes present in the DOM of the opened window. It works fine, never had a problem. PS: assume jQuery is already imported into this project.
openedWindow = window.open("http://localhost/teste1.php");
myClock = window.setInterval(
function() {
if ($(openedWindow.window.document).find("#myElement").length == 1) {
window.clearInterval(openedWindow);
//DO MY STUFF HERE.
}
},
100
);
So yesterday a friend of mine came to me, saw this code and said "You should do that with promises". He said he didnt know how promises worked in depth but he said my code would get smaller and easier to read.
Ok. I dont know promises too. So I studied for over an hour and came up with the code below that works "fine". Except it's a lot bigger and I cant find it easier to read at all.
new Promise(function(resolve,reject) {
openedWindow = window.open("http://localhost/teste1.php");
window.setInterval(
function() {
window.clearInterval(openedWindow);
if ($(openedWindow.window.document).find("#myElement").length == 1) {
resolve(true);
}
},
100
);
}).then(
function(result) {
if (result) {
//DO MY STUFF HERE.
}
}
);
So was my friend wrong? Or I am the wrong one doing the wrong thing with prommises? Is there a better way to do that with promises that I, a newbie in this subject, and not seeing?
Disclamer:
I will leave my answer since I believe it is useful in the context of understanding Promises. The question is related to wait for stuff in the DOM, but I believe OP's main concern is to understand why one would use a Promise over a callback.
However, said so, for the specific problem related to react to DOM changes, Nino's answer is the best.
Original answer
Except it's a lot bigger and I cant find it easier to read at all.
Well, the idea I think is that:
You expose a function like waitForElement which takes an element and returns a Promise that resolves when the element is found.
Now any part of your code can use this function to get a Promise of element being found. This is important because you can attach callbacks to already resolved Promises and still get it run (this is important if you're using a Promise that waits for a database connection, for instance).
So your function would be something like:
function waitForElement(element){
return new Promise(function(resolve) {
// maybe uri could be a param too
openedWindow = window.open("http://localhost/teste1.php");
window.setInterval(
function() {
window.clearInterval(openedWindow);
if ($(openedWindow.window.document).find(element).length == 1) {
resolve(true);
}
},
100
);
});
}
Note the return at the beginning of the method. So now any part of your code could call:
waitForElement('#foo').then( // do my stuff //);
This is still pretty much the same, as you say. However, a good thing about promises is that them allow to attach callbacks, and they cache the async operations, so now you can:
const fooIsPresent = waitForElement('#foo');
// later in your code
fooIsPresent( // some stuff //);
// and even later, maybe minutes later:
fooIsPresent(// still more stuff //);
Now, if the element still is not present, the stuff callbacks will be invoked when the moment arrives.
However, if the element was already found, and you call fooIsPresent after that, the callback will be executed immediately (in the next tick).
And if you want some stuff to always happen before another, you can chain them:
fooIsPresent.then(// first stuff //)
.then(// second stuff //);
So yes, the code is bigger and maybe a bit less clear, but now is more useful and you can use it in some handy ways.
You're both wrong but you're a bit more wrong than him because writing this code as promise allow you to compartiment your code better:
/* without promises */
// code for waiting for an element to appear
// code for after
// code for waiting for an element to appear
/* with promises */
// code for waiting for an element to appear
// code for after
But yeah, you're both wrong because the modern way to wait for an element to appear is to use mutation observers:
const observer = new MutationObserver(mutationRecordList => {
for (mutationRecord of mutationRecordList) {
if (mutationRecord.target.getAttribute('id')=='#my-element') {
console.log('The element just appeared');
}
}
});
observer.observe(document.body, {childList: true, subtree: true});
and wrap the whole thing as a promise, depending on your code.
I agree with your assessment; your Promises version is longer and adds no clarity. Some people think that Promises make all code better, which is just untrue. You can have well-written or poorly-written code, irrespective of whether or not you use Promises. I personally find that clarity is often lost with Promises.
In the first example, I think you meant to pass myClock to clearInterval(). Anyway, I prefer to use setTimeout when I think it will probably be canceled shortly.
I like having a self-contained structure.
(function checker(openedWindow) {
if (openedWindow.window.document.querySelector("#myElement")) {
doMyStuff(openedWindow);
} else {
setTimeout(checker, 100, openedWindow);
}
})(window.open("http://localhost/teste1.php"));
function doMyStuff(openedWindow) {
// DO MY STUFF
}
I am currently building a signboard system that displays timetable information in a consistent format for various locations.
The idea is that each location has its own lightweight page with a small amount of variables that define the location specific parameters and then call the appropriate functions, in order, from a single external .js file.
My page is working fine with functions explicitly chained together, like so:
function one (){
//do the thing
two();
}
function two (){
//do the next thing
three();
}
function three (){
//do the last thing
}
What I am trying to do is separate the functions so that I can call them from a list in each individual page which will let me substitute different versions of certain functions as required in the different locations. Something like this:
function callList(){
one();
//wait for one to finish
two();
//wait for two to finish
three();
}
I have spent a lot of time reading about asynchronous functions, callbacks, promises etc. but the solutions that have been offered still seem to deal more with chaining functions together explicitly and passing a single variable as proof the function has finished, such as this (well written) example:
https://flaviocopes.com/javascript-async-await/
Part of my difficulty in figuring out the right solution is that my functions are quite varied in their purpose. Many of my functions don't produce variables at all and the ones that do (with the exception of a single ajax call) produce large sets of global parameters that don't need to be explicitly passed to the next function. Most, in fact, focus on rendering and manipulating svg and text in various ways, and due to the nature of the data displayed many rely heavily on loops.
As with most javascript problems I encounter I am sure that it is merely a gap in my understanding, but I feel like I am just reading the same articles over and over again and getting nowhere. I really need someone more knowledgeable to give me a nudge in the right direction.
Thanks.
Functions are first-class citizens in Javascript, so you can just throw them into an array and then loop through and call them.
var functionsToCall = [
one,
two,
three
];
// Call them (use your looping method of choice)
for (var i = 0; i < functionsToCall.Length; i++) {
functionsToCall[i]();
}
If your functions are synchronous and are not returning anything that you need, that's basically all you need. If your functions are async, then you might need something more like await functionsToCall[i](); or a setup using promises/callbacks instead.
If you need callbacks to tell you when a function has completed, you can use a small state manager/function to handle that (or you can use async/awaits if your environment will support them - they're cleaner to write! :) ).
Something like...
// A sample async function - you pass the callback to it.
function one(callback) {
// Do some async work, like AJAX...
// Let the callback know when I'm finished (whether I have a value to return or not.
callback();
}
// Simple state management - wrap these up with nicer code and handle errors and whatnot.
var funcIndex = 0;
function callNext() {
if (funcIndex < functionsToCall.Length) {
functionsToCall[funcIndex](callNext);
funcIndex += 1;
}
}
// To start things off:
function callAllFunctions() {
funcIndex = 0;
callNext();
}
If you need to have more granular control over the function calling, you can put custom objects into the array instead of just the functions themselves and change the behavior based on that.
For example:
var functionsToCall = [
{ func: one, isAsync: true },
{ func: two, isAsync: false }
];
Anyway, just some possibilities. It will really depend on exactly what you need for your particular situation!
use await
or
use promises
or
you need function1 execution complete handler will execute next one
I have a webpage, in which a certain Ajax event is triggered asynchronously. This Ajax section could be called once or more than once. I do not have control over the number of times this event is triggered, nor the timing.
Also, there is a certain code in that Ajax section that should run as a critical section, meaning, when it is running, no other copy of that code should be running.
Here is a pseudo code:
Run JavaScript or jQuery code
Enter critical section that is Ajax (when a certain process is waiting for a response callback, then do not enter this section again, until this process is done)
Run more JavaScript or jQuery code
My question is, how can I run step 2 the way described above? How do I create/guarantee a mutual exclusion section using JavaScript or jQuery.
I understand the theory (semaphores, locks, ...etc.), but I could not implement a solution using either JavaScript or jQuery.
EDIT
In case you are suggesting a Boolean variable to get into the critical section, this would not work, and the lines below will explain why.
the code for a critical section would be as follows (using the Boolean variable suggestions):
load_data_from_database = function () {
// Load data from the database. Only load data if we almost reach the end of the page
if ( jQuery(window).scrollTop() >= jQuery(document).height() - jQuery(window).height() - 300) {
// Enter critical section
if (window.lock == false) {
// Lock the critical section
window.lock = true;
// Make Ajax call
jQuery.ajax({
type: 'post',
dataType: 'json',
url: path/to/script.php,
data: {
action: 'action_load_posts'
},
success: function (response) {
// First do some stuff when we get a response
// Then we unlock the critical section
window.lock = false;
}
});
// End of critical section
}
}
};
// The jQuery ready function (start code here)
jQuery(document).ready(function() {
var window.lock = false; // This is a global lock variable
jQuery(window).on('scroll', load_data_from_database);
});
Now this is the code for the lock section as suggested using a Boolean variable. This would not work as suggested below:
The user scrolls down, (and based on the association jQuery(window).on('scroll', load_data_from_database); more than one scroll event is triggered.
Assume two scroll events are triggered right at almost the same moment
Both call the load_data_from_database function
The first event checks if window.lock is false (answer is true, so if statement is correct)
The second event checks if window.lock is false (answer is true, so if statement is correct)
The first event enters the if statement
The second event enters the if statement
The first statement sets window.lock to true
The second statement sets window.lock to true
The first statement runs the Ajax critical section
The second statement runs the Ajax critical section.
Both finish the code
As you notice, both events are triggered almost at the same time, and both enter the critical section. So a lock is not possible.
I think the most helpful information you provided above was your analysis of the locking.
The user scrolls down, (and based on the association jQuery(window).on('scroll', load_data_from_database); more than one
scroll event is triggered.
Assume two scroll events are triggered right at almost the same moment
Both call the load_data_from_database function
The first event checks if window.lock is false (answer is true, so if statement is correct)
The second event checks if window.lock is false (answer is true, so if statement is correct)
Right away this tells me that you have come to a common (and quite intuitive) misunderstanding.
Javascript is asynchronous, but asynchronous code is not the same thing as concurrent code. As far as I understand, "asynchronous" means that a function's subroutines aren't necessarily explored in depth-first order as we would expect in synchronous code. Some function calls (the ones you are calling "ajax") will be put in a queue and executed later. This can lead to some confusing code, but nothing is as confusing as thinking that your async code is running concurrently. "Concurrency" (as you know) is when statements from different functions can interleave with one another.
Solutions like locks and semaphores are not the right way to think about async code. Promises are the right way. This is the stuff that makes programming on the web fun and cool.
I'm no promise guru, but here is a working fiddle that (I think) demonstrates a fix.
load_data_from_database = function () {
// Load data from the database. Only load data if we almost reach the end of the page
if ( jQuery(window).scrollTop() >= jQuery(document).height() - jQuery(window).height() - 300) {
console.log(promise.state());
if (promise.state() !== "pending") {
promise = jQuery.ajax({
type: 'post',
url: '/echo/json/',
data: {
json: { name: "BOB" },
delay: Math.random() * 10
},
success: function (response) {
console.log("DONE");
}
});
}
}
};
var promise = new $.Deferred().resolve();
// The jQuery ready function (start code here)
jQuery(document).ready(function() {
jQuery(window).on('scroll', load_data_from_database);
});
I'm using a global promise to ensure that the ajax part of your event handler is only called once. If you scroll up and down in the fiddle, you will see that while the ajax request is processing, new requests won't be made. Once the ajax request is finished, new requests can be made again. With any luck, this is the behaviour you were looking for.
However, there is a pretty important caveats to my answer: jQuery's implementation of promises is notoriously broken. This isn't just something that people say to sound smart, it is actually pretty important. I would suggest using a different promise library and mixing it with jQuery. This is especially important if you are just starting to learn about promises.
EDIT: On a personal note, I was recently in the same boat as you. As little as 3 months ago, I thought that some event handlers I was using were interleaving. I was stupefied and unbelieving when people started to tell me that javascript is single-threaded. What helped me is understanding what happens when an event is fired.
In syncronous coding, we are used to the idea of a "stack" of "frames" each representing the context of a function. In javascript, and other asynchronous programming environments, the stack is augmented by a queue. When you trigger an event in your code, or use an asynchronous request like that $.ajax call, you push an event to this queue. The event will be handled the next time that the stack is clear. So for example, if you have this code:
function () {
this.on("bob", function () { console.log("hello"); })
this.do_some_work();
this.trigger("bob");
this.do_more_work();
}
The two functions do_some_work and do_more_work will fire one after the other, immediately. Then the function will end and the event you enqueued will start a new function call, (on the stack) and "hello" will appear in the console. Things get more complicated if you trigger an event in your handler, or if you trigger and event in a subroutine.
This is all well and good, but where things start to get really crappy is when you want to handle an exception. The moment you enter asynchronous land, you leave behind the beautiful oath of "a function shall return or throw". If you are in an event handler, and you throw an exception, where will it be caught? This,
function () {
try {
$.get("stuff", function (data) {
// uh, now call that other API
$.get("more-stuff", function (data) {
// hope that worked...
};
});
} catch (e) {
console.log("pardon me?");
}
}
won't save you now. Promises allow you to take back this ancient and powerful oath by giving you a way to chain your callbacks together and control where and when they return. So with a nice promises API (not jQuery) you chain those callbacks in a way that lets you bubble exceptions in the way you expect, and to control the order of execution. This, in my understanding, is the beauty and magic of promises.
Someone stop me if I'm totally off.
I would recommend a queue which only allows one item to be running at a time. This will require some modification (though not much) to your critical function:
function critical(arg1, arg2, completedCallback) {
$.ajax({
....
success: function(){
// normal stuff here.
....
// at the end, call the completed callback
completedCallback();
}
});
}
var queue = [];
function queueCriticalCalls(arg1, arg2) {
// this could be done abstractly to create a decorator pattern
queue.push([arg1, arg2, queueCompleteCallback]);
// if there's only one in the queue, we need to start it
if (queue.length === 1) {
critical.apply(null, queue[0]);
}
// this is only called by the critical function when one completes
function queueCompleteCallback() {
// clean up the call that just completed
queue.splice(0, 1);
// if there are any calls waiting, start the next one
if (queue.length !== 0) {
critical.apply(null, queue[0]);
}
}
}
UPDATE: Alternative solution using jQuery's Promise (requires jQuery 1.8+)
function critical(arg1, arg2) {
return $.ajax({
....
});
}
// initialize the queue with an already completed promise so the
// first call will proceed immediately
var queuedUpdates = $.when(true);
function queueCritical(arg1, arg2) {
// update the promise variable to the result of the new promise
queuedUpdates = queuedUpdates.then(function() {
// this returns the promise for the new AJAX call
return critical(arg1, arg2);
});
}
Yup, the Promise of cleaner code was realized. :)
You can wrap the critical section in a function and then swap the function so it does nothing after first run:
// this function does nothing
function noop() {};
function critical() {
critical = noop; // swap the functions
//do your thing
}
Inspired by user #I Hate Lazy Function in javascript that can be called only once
So, I have a page that loads and through jquery.get makes several requests to populate drop downs with their values.
$(function() {
LoadCategories($('#Category'));
LoadPositions($('#Position'));
LoadDepartments($('#Department'));
LoadContact();
};
It then calls LoadContact(); Which does another call, and when it returns it populates all the fields on the form. The problem is that often, the dropdowns aren't all populated, and thus, it can't set them to the correct value.
What I need to be able to do, is somehow have LoadContact only execute once the other methods are complete and callbacks done executing.
But, I don't want to have to put a bunch of flags in the end of the drop down population callbacks, that I then check, and have to have a recursive setTimeout call checking, prior to calling LoadContact();
Is there something in jQuery that allows me to say, "Execute this, when all of these are done."?
More Info
I am thinking something along these lines
$().executeAfter(
function () { // When these are done
LoadCategories($('#Category'));
LoadPositions($('#Position'));
LoadDepartments($('#Department'));
},
LoadContact // Do this
);
...it would need to keep track of the ajax calls that happen during the execution of the methods, and when they are all complete, call LoadContact;
If I knew how to intercept ajax that are being made in that function, I could probably write a jQuery extension to do this.
My Solution
;(function($) {
$.fn.executeAfter = function(methods, callback) {
var stack = [];
var trackAjaxSend = function(event, XMLHttpRequest, ajaxOptions) {
var url = ajaxOptions.url;
stack.push(url);
}
var trackAjaxComplete = function(event, XMLHttpRequest, ajaxOptions) {
var url = ajaxOptions.url;
var index = jQuery.inArray(url, stack);
if (index >= 0) {
stack.splice(index, 1);
}
if (stack.length == 0) {
callback();
$this.unbind("ajaxComplete");
}
}
var $this = $(this);
$this.ajaxSend(trackAjaxSend)
$this.ajaxComplete(trackAjaxComplete)
methods();
$this.unbind("ajaxSend");
};
})(jQuery);
This binds to the ajaxSend event while the methods are being called and keeps a list of urls (need a better unique id though) that are called. It then unbinds from ajaxSend so only the requests we care about are tracked. It also binds to ajaxComplete and removes items from the stack as they return. When the stack reaches zero, it executes our callback, and unbinds the ajaxComplete event.
You can use .ajaxStop() like this:
$(function() {
$(document).ajaxStop(function() {
$(this).unbind("ajaxStop"); //prevent running again when other calls finish
LoadContact();
});
LoadCategories($('#Category'));
LoadPositions($('#Position'));
LoadDepartments($('#Department'));
});
This will run when all current requests are finished then unbind itself so it doesn't run if future ajax calls in the page execute. Also, make sure to put it before your ajax calls, so it gets bound early enough, it's more important with .ajaxStart(), but best practice to do it with both.
Expanding on Tom Lianza's answer, $.when() is now a much better way to accomplish this than using .ajaxStop().
The only caveat is that you need to be sure the asynchronous methods you need to wait on return a Deferred object. Luckily jQuery ajax calls already do this by default. So to implement the scenario from the question, the methods that need to be waited on would look something like this:
function LoadCategories(argument){
var deferred = $.ajax({
// ajax setup
}).then(function(response){
// optional callback to handle this response
});
return deferred;
}
Then to call LoadContact() after all three ajax calls have returned and optionally executed their own individual callbacks:
// setting variables to emphasize that the functions must return deferred objects
var deferred1 = LoadCategories($('#Category'));
var deferred2 = LoadPositions($('#Position'));
var deferred3 = LoadDepartments($('#Department'));
$.when(deferred1, deferred2, deferred3).then(LoadContact);
If you're on Jquery 1.5 or later, I suspect the Deferred object is your best bet:
http://api.jquery.com/category/deferred-object/
The helper method, when, is also quite nice:
http://api.jquery.com/jQuery.when/
But, I don't want to have to put a bunch of flags in the end of the drop down population callbacks, that I then check, and have to have a recursive setTimeout call checking, prior to calling LoadContact();
No need for setTimeout. You just check in each callback that all three lists are populated (or better setup a counter, increase it in each callback and wait till it's equal to 3) and then call LoadContact from callback. Seems pretty easy to me.
ajaxStop approach might work to, I'm just not very familiar with it.
I have a function that I want to use deferred and promise to chain an animation.
The first animation is a type writer plugin, using https://github.com/stephband/jticker/blob/master/js/jquery.jticker.js.
The second is a function that contains other animations.
What I want to do is run the first animation and when the animation is completed, run the second.
$(function () {
var ticker =setTimeout(runTicker(), 8000);
$.when(ticker).done(function(){
setTimeout(Other(),16000)});
});
function runTicker() {
$("#ticker").ticker({
cursorList: " ",
rate: 50,
delay: 18000
}).trigger("play").trigger("stop");
}
I have tried numerous examples of deferred, but still can't get it.
I finally cleared all the examples in order to get the ticker working again.
How would I use deferred and promise to run the Other() function?
Thank you
Don't know how to solve your actual problem with a proper callback-based solution (not enough information on the Ticker plugin you use), but I can explain what goes wrong in your current code:
var ticker = setTimeout(runTicker(), 8000);
Don't call runTicker immediately. What you want is to pass the function itself - not the result of its invocation - into setTimeout. A [plain integer] number will be returned and is assigned to ticker. It can be used for identifying the timeout when aborting it via clearTimeout - and nowhere else.
$.when(ticker)...
creates a new Deferred now. Have a look at its documentation: It will combine Deferred objects with each other and create immediately-resolved Promises for any other values - like numbers. Therefore, your done callback is also called immidiately, and again you make the mistake with executing Other instead of passing it into setTimeout.
As the plugin you used seems very limited in regard to callbacks, I've written my own now (just for fun :-). It adapts my solution from this former answer which uses pure DOM methods quite elegantly. It is written as a standard jQuery plugin, even favours stop and go methods and - most important - integrates well in the jQuery fx queue. That means you will be able to use it exactly like animate() regarding callbacks and chaining, and if you want to work with Deferreds you can invoke the promise() method to get a Promise for the queue's end. Example call:
$('#ticker').fadeOut().delay(2000).typewriter({framerate:1000/30}, function() {
// the typewriter effect has just ended
}). ... .promise().done(function() {
// effect queue has ended - start Other()
});
jQuery(".stop").click($.fn.typewriter.bind($("#ticker"), "stop"));
→ Code at jsfiddle.net
setTimeout obviously will not return a jQuery Deferred object, its a native Javascript method here. You need to re-write it like so:
function runTicker() {
return jQuery.Deferred(function( promise ) {
setTimeout(function() {
$("#ticker").ticker({
cursorList: " ",
rate: 50,
delay: 18000
}).trigger("play").trigger("stop");
promise.resolve();
}, 8000);
}).promise();
}
And then you can call it like
var ticker = runTicker();
jQuery.when( ticker ).done(function() {
setTimeout(Other,16000)});
});