I have a click event that is triggered from another place automatically for the first time. My problem is that it runs too soon, since the required variables are still being defined by Flash and web services. So right now I have:
(function ($) {
$(window).load(function(){
setTimeout(function(){
$('a.play').trigger("click");
}, 5000);
});
})(jQuery);
The problem is that 5 seconds for a person with a slow internet connection could be too fast and vice versa, for a person with a fast internet connection, it's too slow.
So how should I do the delay or timeout until someVariable is defined?
The following will keep looking for someVariable until it is found. It checks every 0.25 seconds.
function waitForElement(){
if(typeof someVariable !== "undefined"){
//variable exists, do what you want
}
else{
setTimeout(waitForElement, 250);
}
}
async, await implementation, improvement over #Toprak's answer
(async() => {
console.log("waiting for variable");
while(!window.hasOwnProperty("myVar")) // define the condition as you like
await new Promise(resolve => setTimeout(resolve, 1000));
console.log("variable is defined");
})();
console.log("above code doesn't block main function stack");
After revisiting the OP's question. There is actually a better way to implement what was intended: "variable set callback". Although the below code only works if the desired variable is encapsulated by an object (or window) instead of declared by let or var (I left the first answer because I was just doing improvement over existing answers without actually reading the original question):
let obj = encapsulatedObject || window;
Object.defineProperty(obj, "myVar", {
configurable: true,
set(v){
Object.defineProperty(obj, "myVar", {
configurable: true, enumerable: true, writable: true, value: v });
console.log("window.myVar is defined");
}
});
see Object.defineProperty
or use es6 proxy (which is probably overkill)
If you are looking for more:
/**
* combining the two as suggested by #Emmanuel Mahuni,
* and showing an alternative to handle defineProperty setter and getter
*/
let obj = {} || window;
(async() => {
let _foo = await new Promise(res => {
Object.defineProperty(obj, "foo", { set: res });
});
console.log("obj.foo is defined with value:", _foo);
})();
/*
IMPORTANT: note that obj.foo is still undefined
the reason is out of scope of this question/answer
take a research of Object.defineProperty to see more
*/
// TEST CODE
console.log("test start");
setTimeout(async () => {
console.log("about to assign obj.foo");
obj.foo = "Hello World!";
// try uncomment the following line and compare the output
// await new Promise(res => setTimeout(res));
console.log("finished assigning obj.foo");
console.log("value of obj.foo:", obj.foo); // undefined
// console: obj.foo is defined with value: Hello World!
}, 2000);
I would prefer this code:
function checkVariable() {
if (variableLoaded == true) {
// Here is your next action
}
}
setTimeout(checkVariable, 1000);
I prefer something simple like this:
function waitFor(variable, callback) {
var interval = setInterval(function() {
if (window[variable]) {
clearInterval(interval);
callback();
}
}, 200);
}
And then to use it with your example variable of someVariable:
waitFor('someVariable', function() {
// do something here now that someVariable is defined
});
Note that there are various tweaks you can do. In the above setInterval call, I've passed 200 as how often the interval function should run. There is also an inherent delay of that amount of time (~200ms) before the variable is checked for -- in some cases, it's nice to check for it right away so there is no delay.
With Ecma Script 2017 You can use async-await and while together to do that
And while will not crash or lock the program even variable never be true
//First define some delay function which is called from async function
function __delay__(timer) {
return new Promise(resolve => {
timer = timer || 2000;
setTimeout(function () {
resolve();
}, timer);
});
};
//Then Declare Some Variable Global or In Scope
//Depends on you
let Variable = false;
//And define what ever you want with async fuction
async function some() {
while (!Variable)
await __delay__(1000);
//...code here because when Variable = true this function will
};
////////////////////////////////////////////////////////////
//In Your Case
//1.Define Global Variable For Check Statement
//2.Convert function to async like below
var isContinue = false;
setTimeout(async function () {
//STOPT THE FUNCTION UNTIL CONDITION IS CORRECT
while (!isContinue)
await __delay__(1000);
//WHEN CONDITION IS CORRECT THEN TRIGGER WILL CLICKED
$('a.play').trigger("click");
}, 1);
/////////////////////////////////////////////////////////////
Also you don't have to use setTimeout in this case just make ready function asynchronous...
You can use this:
var refreshIntervalId = null;
refreshIntervalId = setInterval(checkIfVariableIsSet, 1000);
var checkIfVariableIsSet = function()
{
if(typeof someVariable !== 'undefined'){
$('a.play').trigger("click");
clearInterval(refreshIntervalId);
}
};
Here's an example where all the logic for waiting until the variable is set gets deferred to a function which then invokes a callback that does everything else the program needs to do - if you need to load variables before doing anything else, this feels like a neat-ish way to do it, so you're separating the variable loading from everything else, while still ensuring 'everything else' is essentially a callback.
var loadUser = function(everythingElse){
var interval = setInterval(function(){
if(typeof CurrentUser.name !== 'undefined'){
$scope.username = CurrentUser.name;
clearInterval(interval);
everythingElse();
}
},1);
};
loadUser(function(){
//everything else
});
Instead of using the windows load event use the ready event on the document.
$(document).ready(function(){[...]});
This should fire when everything in the DOM is ready to go, including media content fully loaded.
Shorter way:
var queue = function (args){
typeof variableToCheck !== "undefined"? doSomething(args) : setTimeout(function () {queue(args)}, 2000);
};
You can also pass arguments
I have upvoted #dnuttle's answer, but ended up using the following strategy:
// On doc ready for modern browsers
document.addEventListener('DOMContentLoaded', (e) => {
// Scope all logic related to what you want to achieve by using a function
const waitForMyFunction = () => {
// Use a timeout id to identify your process and purge it when it's no longer needed
let timeoutID;
// Check if your function is defined, in this case by checking its type
if (typeof myFunction === 'function') {
// We no longer need to wait, purge the timeout id
window.clearTimeout(timeoutID);
// 'myFunction' is defined, invoke it with parameters, if any
myFunction('param1', 'param2');
} else {
// 'myFunction' is undefined, try again in 0.25 secs
timeoutID = window.setTimeout(waitForMyFunction, 250);
}
};
// Initialize
waitForMyFunction();
});
It is tested and working! ;)
Gist: https://gist.github.com/dreamyguy/f319f0b2bffb1f812cf8b7cae4abb47c
Object.defineProperty(window, 'propertyName', {
set: value => {
this._value = value;
// someAction();
},
get: () => this._value
});
or even if you just want this property to be passed as an argument to a function and don't need it to be defined on a global object:
Object.defineProperty(window, 'propertyName', { set: value => someAction(value) })
However, since in your example you seem to want to perform an action upon creation of a node, I would suggest you take a look at MutationObservers.
I have an adaptation of the answer by #dnuttle that I would suggest using.
The advantage of using a try-catch block is that if any part of the code you are trying to execute fails, the whole block fails. I find this useful because it gives you a kind of transaction; everything or nothing gets done.
You should never write code that could end up in an endless loop due to external factors. This is exactly what would happen if you were waiting for a response from an ajax request and the server doesn't respond. I think it's good practice to have a timeout for any questionable loops.
let time = 0; // Used to keep track of how long the loop runs
function waitForElement() {
try {
// I'm testing for an element, but this condition can be
// any reasonable condition
if (document.getElementById('test') === null) {
throw 'error';
}
// This is where you can do something with your variable
// document.getElementById('test').addEventListener....
// or call a function that uses your value
} catch (error) {
// Loop stops executing if not successful within about 5 seconds
if (time > 5000) {
// I return false on failure so I can easily check for failure
return false;
} else {
// Increment the time and call the function again
time += 250;
setTimeout(waitForElement, 250);
}
}
}
// Call the function after the definition, ensures that time is set
waitForElement();
You could have Flash call the function when it's done. I'm not sure what you mean by web services. I assume you have JavaScript code calling web services via Ajax, in which case you would know when they terminate. In the worst case, you could do a looping setTimeout that would check every 100 ms or so.
And the check for whether or not a variable is defined can be just if (myVariable) or safer: if(typeof myVariable == "undefined")
Very late to the party but I want to supply a more modern solution to any future developers looking at this question. It's based off of Toprak's answer but simplified to make it clearer as to what is happening.
<div>Result: <span id="result"></span></div>
<script>
var output = null;
// Define an asynchronous function which will not block where it is called.
async function test(){
// Create a promise with the await operator which instructs the async function to wait for the promise to complete.
await new Promise(function(resolve, reject){
// Execute the code that needs to be completed.
// In this case it is a timeout that takes 2 seconds before returning a result.
setTimeout(function(){
// Just call resolve() with the result wherever the code completes.
resolve("success output");
}, 2000);
// Just for reference, an 'error' has been included.
// It has a chance to occur before resolve() is called in this case, but normally it would only be used when your code fails.
setTimeout(function(){
// Use reject() if your code isn't successful.
reject("error output");
}, Math.random() * 4000);
})
.then(function(result){
// The result variable comes from the first argument of resolve().
output = result;
})
.catch(function(error){
// The error variable comes from the first argument of reject().
// Catch will also catch any unexpected errors that occur during execution.
// In this case, the output variable will be set to either of those results.
if (error) output = error;
});
// Set the content of the result span to output after the promise returns.
document.querySelector("#result").innerHTML = output;
}
// Execute the test async function.
test();
// Executes immediately after test is called.
document.querySelector("#result").innerHTML = "nothing yet";
</script>
Here's the code without comments for easy visual understanding.
var output = null;
async function test(){
await new Promise(function(resolve, reject){
setTimeout(function(){
resolve("success output");
}, 2000);
setTimeout(function(){
reject("error output");
}, Math.random() * 4000);
})
.then(function(result){
output = result;
})
.catch(function(error){
if (error) output = error;
});
document.querySelector("#result").innerHTML = output;
}
test();
document.querySelector("#result").innerHTML = "nothing yet";
On a final note, according to MDN, Promises are supported on all modern browsers with Internet Explorer being the only exception. This compatibility information is also supported by caniuse. However with Bootstrap 5 dropping support for Internet Explorer, and the new Edge based on webkit, it is unlikely to be an issue for most developers.
while (typeof myVar == void(0)) {
if ( typeof myVar != void(0)) {
console.log(myVar);
}
}
This makes use of the typeof operator which only returns undefined if variable is not declared yet. Should be applicable in every type of javascript.
I would finally like to reach some clarity on this aspect of programming, being basically self-taught. It's to do with passing variables around between functions.
With the code below, data.roomId is not recognized in the first function (obviously). I could place the function inside the joinRoom function to make it recognize data.roomId, but then how does the leaveRoom function recognize sendHeartbeat?
`io.on('connection', function (socket) {
function sendHeartbeat(){
setTimeout(sendHeartbeat, 8000);
socket.broadcast.to(data.roomId).emit('ping', { beat : 1 });
}
socket.on('joinRoom', function (data) {
socket.join(data.roomId)
setTimeout(sendHeartbeat, 8000);
});
socket.on('leaveRoom', function (data) {
socket.leave(data.roomId)
clearTimeout(sendHeartbeat)
});
});`
You can do what we call a closure, it is a function that returns a function keeping track of local variables. It is a cool concept !
function sendHeartbeat(roomId){
return function() {
socket.broadcast.to(roomId).emit('ping', { beat : 1 });
}
}
socket.on('joinRoom', function (data) {
socket.join(data.roomId)
setTimeout(sendHeartbeat(data.roomId), 8000);
});
You call the function sendHeartbeat passing it the roomId, and it returns the function you want to be executed in your setTimeout :)
Is it possible to pass a callback function that does not exist yet? My goal is to have a common function that will wait for another callback function to exist, when it does exist, it should execute it. This is what I have so far, but I can't figure out how to pass the function in that doesn't exist as a function yet.
function RunTemplateFunction(callback, userInfo) {
if ($.isFunction(callback)) {
callback(userInfo);
} else {
var myInterval = setInterval(function () {
if ($.isFunction(callback)) {
clearInterval(myInterval);
callback(userInfo);
}
}, 200);
}
}
I run the function like this:
RunTemplateFunction(MyFunctionToRun, GetUserInfo());
I get MyFunctionToRun is undefined for obvious reasons, I also tried the workaround of passing the function as a string and then convert the string to a function using eval(). But that throws the same error. I also thought of using the new function(), but that actually creates a new function.
Any help is appreciated. thank you.
If you call RunTemplateFunction by undefined there is no way we can see, is callback is defined or not, as we don't have reference to anything.
If you can modify the declaration to accept object as below, we can achieve what we want
function RunTemplateFunction(options, userInfo) {
if ($.isFunction(options.callback)) {
console.log('called1',userInfo);
options.callback(userInfo);
} else {
var myInterval = setInterval(function () {
if ($.isFunction(options.callback)) {
console.log('Called dynamically!!');
clearInterval(myInterval);
options.callback(userInfo);
}
}, 200);
}
}
var options = {}
RunTemplateFunction(options,{user:122});
options.callback = function(){
console.log("I'm called!!");
}
This will print
Called dynamically!!
I'm called!!
EDIT:
We can also call callback function in following way without setInterval, it will look different but options.callback variable is replaced by template.callMe function and its instantaneous also.
function TemplateRunner(userInfo){
this.callMe = function(cb){
this.templateFunction(cb);
}
this.templateFunction = function(callback){
callback(userInfo);
}
}
var template = new TemplateRunner({user:100})
template.callMe(function(user){
console.log('call me1',user);
});
template.callMe(function(user){
console.log('call me2',user);
})
This will print
call me1 {user: 100}
call me2 {user: 100}
I'm trying to figure out how I can reset a timer created inside of an immediately invoking function from within the setTimeout closure. Here is my function:
var triggerHeightRecalc = function() {
setTimeout(function() {
if(imagesLoaded()) {
adjustHeight();
} else {
triggerHeightRecalc();
}
}, 100);
}();
In the event that imagesLoaded() returns false, I receive the following error from attempting to call triggerHeightRecalc():
Uncaught TypeError: undefined is not a function
So I'm not sure if the issue is the function is not in the scope, or maybe it just cannot call itself? I've tried passing triggerHeightRecalc as a parameter in the setTimeout closure, but that doesn't seem to work either.
I've also tried this after reading this SO question:
var triggerHeightRecalc = function() {
var that = this;
var callback = function() {
if(imagesLoaded()) {
adjustHeight();
} else {
that.triggerHeightRecalc();
}
};
timeDelay = window.setTimeout(callback, 100);
}();
What am I doing wrong here, or is there a better way? Is this something that should be a setInterval() instead and I clear the interval when images are loaded?
Side Note: I'm calculating the height of a div inside a jQuery plugin, but I need to wait until the images are loaded in order to get the correct height (not sure if that is relevant).
Since you are invoking the function right from the declaration triggerHeightRecalc is getting set to the return of that function call, which is undefined since you in fact do not return anything.
You can do two things
1. Declare then invoke
var triggerHeightRecalc = function() {
setTimeout(function() {
if(imagesLoaded()) {
adjustHeight();
} else {
triggerHeightRecalc();
}
}, 100);
};
triggerHeightRecalc();
2. Wrap the declaration in () and invoke
var triggerHeightRecalc;
(triggerHeightRecalc = function() {
setTimeout(function() {
if(imagesLoaded()) {
adjustHeight();
} else {
triggerHeightRecalc();
}
}, 100);
})();
The second one will create a global variable unless you do the var triggerHeightRecalc; before hand.
Already answered, but I'll put this in.
First of all, if you just want to wait until all images have loaded you can use:
https://github.com/desandro/imagesloaded and then run the above code.
If that's not what you want, and you you just want a function that your setTimeout can run, then you can remove the () at the end of the function.
Here is what's happening in your current code
Your function is missing the opening bracket or similar character !+( (function.
Also your IIFE has no return keyword, and will return undefined to triggerHeightCalc.
If you do want an IIFE then you can either have a private version that is only callable within itself.
(function myModule(){
myModule(); //calls itself
})();
Or a public version that can be called both inside and outside.
var myModule = (function(){
return function myMod(){
myMod();
}
})();
myModule();
Patrick Evans has the right reasons, but there is a neater way to solve it :)
(function triggerHeightRecalc() {
setTimeout(function() {
if(imagesLoaded()) {
adjustHeight();
} else {
triggerHeightRecalc();
}
}, 100);
})();
Here you are give an internal name to the (still) anonymous function. The name is only visible from within the function itself, its not visible in the global scope. Its called a Named function expression.
I know my problem I just not sure how to resolve it. I have a custom domain and in a function call a while loop executes. In that loop i wanted an animation to occur in order.
So the first problem is that javascript by its nature executes every line thus item 2 starts before item 1 completes. Now the effect is so short that it "appears" to happen to all elements at once but in the debugger it is just looping one at a time.
Now my typical resolution would be to use SetTimeout() but that is causing the browser to lock. Reading this post (Trying to delay/pause/slow a while loop in jQuery) it makes sense that the browser is getting into an endless loop.
So how can I get a pause between element1 and element2 events? I thought perhaps to add a callback function to my custom domain but not sure if that will work as desired besides not being sure how to do it.
In the head of the page and read the comments for anything else I may be doing wrong or could do better.
$(document).ready(function ()
{
//pause long enough for person to visually take in page before starting
setTimeout(function () { PageLoadAnimation.onReady(); }, 1000);
});
My custom domain:
var PageLoadAnimation =
{
onReady: function ()
{
//black everything out just to be sure
PageLoadAnimation.BlackOutElements();
//flash & show
PageLoadAnimation.FlashElement();
},
BlackOutElements: function ()
{
$('#ParentContainer').children().hide();
},
FlashElement: function ()
{
//get array of all elements and loop till all are visible
var elementArray = $('#ParentContainer').children();
var $els = $('#PartialsContainer').children();
while (elementArray.length)
{
var $el = elementArray.eq(Math.floor(Math.random() * elementArray.length));
//if I put set timeout here is causes the infinite loop
PageLoadAnimation.FlashBlast($el);
elementArray = elementArray.not($el);
//if I put by itself it no diff as the while loop continues regardless
//setTimeout(1500);
}
},
FlashBlast: function ($el)
{
//flash background
$el.fadeIn(200, function () { $el.fadeOut(200) });
}
}
I'm not sure if it isn't working or if I am doing something wrong so I created these fiddles:
Original Fiddle
With Johan Callbacks
Using is animating property
WARNING THIS ONE WILL HANG YOUR BROWSER!
I don't think I am checking the isAnimating property the way Johan had in mind??
ANSWER FOR THIS SITUATION. Hopefully it will help others.
setTimeout in a loop was really my problem...but not the only problem. I was the other problem(s).
Me first.
Fool that I am I was really causing my own complications with two things I was doing wrong.
First using jsfiddle my javascript would error due to syntax or some such thing but fiddle doesn't tell you that (to my knowledge) so my fiddle wouldn't run but I took it in pride as MY CODE IS FINE stupid javascript isn't working.
Second I was passing my function to setTimeout incorrectly. I was adding the function parens () and that is not correct either which would bring me back to issue one above.
WRONG: intervalTimer = setInterval(MyFunction(), 1500);
RIGHT: intervalTimer = setInterval(MyFunction, 1500);
As for the code I read here (http://javascript.info/tutorial/settimeout-setinterval) setting a timeout in a loop is bad. The loop will iterate rapidly and with the timeout one of the steps in the loop we get into a circular firing squad.
Here is my implementation:
I created a couple variables but didn't want them polluting the global scope so I created them within the custom domain. One to hold the array of elements the other the handle to the setInterval object.
var PageLoadAnimation =
{
elementArray: null,
intervalTimer: null,
....
}
In my onReady function (the one the page calls to kick things off) I set my domain array variable and set the interval saving the handle for use later. Note that the interval timer is how long I want between images flashes.
onReady: function ()
{
elementArray = $('#PartialsContainer').children();
//black everything out just to be sure
PageLoadAnimation.BlackOutElements();
//flash & show
intervalTimer = setInterval(PageLoadAnimation.FlashElement, 1500);
},
Now instead of looping through the array I am executing a function at certain intervals and just tracking how many elements are left in the array to be flashed. Once there are zero elements in the array I kill the interval execution.
FlashElement: function ()
{
if(elementArray.length > 0) //check how many elements left to be flashed
{
var $el = PageLoadAnimation.GrabElement(); //get random element
PageLoadAnimation.FlashBlast($el); //flash it
PageLoadAnimation.RemoveElement($el); //remove that element
}
else
{
//done clear timer
clearInterval(intervalTimer);
intervalTimer = null;
}
},
So the whole thing is:
var PageLoadAnimation =
{
elementArray: null,
intervalTimer: null,
onReady: function () {
elementArray = $('#PartialsContainer').children();
//black everything out just to be sure
PageLoadAnimation.BlackOutElements();
//flash & show
intervalTimer = setInterval(PageLoadAnimation.FlashElement, 1500);
//NOT this PageLoadAnimation.FlashElement()
},
BlackOutElements: function () {
$('#PartialsContainer').children().hide();
},
FlashElement: function ()
{
if(elementArray.length > 0)
{
var $el = PageLoadAnimation.GrabElement();
PageLoadAnimation.FlashBlast($el);
PageLoadAnimation.RemoveElement($el);
}
else
{
//done clear timer
clearInterval(intervalTimer);
intervalTimer = null;
}
},
GrabElement: function()
{
return elementArray.eq(Math.floor(Math.random() * elementArray.length));
},
RemoveElement: function($el)
{ elementArray = elementArray.not($el); },
FlashBlast: function ($el) {
//flash background
$el.fadeIn(100, function () { $el.fadeOut(100) });
}
}
Hope that help others understand the way to go about pausing execution in javascript.
A callback example that might help:
FlashBlast: function ($el, fadeInComplete, fadeOutComplete)
{
if(arguments.length === 3){
$el.fadeIn(200, function () {
fadeInComplete();
$el.fadeOut(200, fadeOutComplete);
});
}
}
Usage:
PageLoadAnimation.FlashBlast($el, function(){
//fadein complete
}, function(){
//fadeout complete
});
Another idea that might help:
isAnimating: false,
FlashBlast: function ($el)
{
var dfd = $.Deferred(),
that = this;
that.isAnimating = true;
$el.fadeIn(200, function () {
$el.fadeOut(200, function(){
dfd.resolve();
})
});
dfd.done(function(){
that.isAnimating = false;
});
}
Then make use of the private property isAnimating.
Finally, to know if an element is under an animation, you can use $el.is(':animated').
Hope this helps. Let me know if anything is unclear.