96

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?

Pang
  • 9,564
  • 146
  • 81
  • 122
JackLeo
  • 4,579
  • 9
  • 40
  • 66
  • Is there no "ready" event you can react to? – Tomalak Sep 05 '11 at 12:24
  • 1
    Hmm, there's no generic way to tell "wait until certain variable is defined" (in dumb implementation it would be checking every X miliseconds whenever the variable is defined yet). However if the variables you're waiting for are set by your (I mean managed by you) code then you can define a named function (a callback) that will be called right after the desired variable definition. – WTK Sep 05 '11 at 12:25
  • There should be watch -or something like that- method exist in jQuery. – Tim Sep 05 '11 at 12:26

15 Answers15

155

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);
    }
}
marc_aragones
  • 4,344
  • 4
  • 26
  • 38
dnuttle
  • 3,810
  • 2
  • 19
  • 19
  • 3
    I should add the the comment suggesting "ready" is probably better; my solution will work with or without jQuery. – dnuttle Sep 05 '11 at 12:24
  • 9
    The extra function is unnecessary. Do `setTimeout(waitForElement,250);` – Tomalak Sep 05 '11 at 12:27
  • True. I tend to use anonymous functions as a matter of course, because frequently I need to be able to pass parameter values back to the outer function. – dnuttle Sep 05 '11 at 12:38
  • None of this is required here. The wrapper function is plainly superfluous. You know, KISS/YAGNI. :) – Tomalak Sep 05 '11 at 12:39
  • 2
    `setInterval()` seems like a better fit here – Stefan Schmidt Jul 28 '12 at 22:24
  • 2
    setInterval() would be wrong. It doesnt need to keep calling the function once someVariable is defined. – matt2000 Dec 15 '16 at 23:07
  • How can I make this an anonymous function? PS: Google took me here. – OnklMaps Apr 04 '18 at 13:17
  • 3
    I know this is old, but I stumbled upon this today. The wrapper function is not superfluous. It creates a closure around the code creating a module that keeps it private. That's good practice and keeps the window global from being clogged up with unnecessary code. – mccambridge Jun 06 '18 at 14:33
  • setInterval() is not exactly wrong but you would have to call clearInterval() once inside the 'if' condition. – Krishnaraj Jan 24 '19 at 06:13
  • This is a working solution however I was wondering, if I could pass `someVariable` and the function (do what you want) as variables of `waitForElement` like so `waitForElement(var_to_listen, fun_to_execute)`. Thus, I called the function like so: `waitForElement(myvar,myfunc)`, however, this results in myvar never being defined (even if it is) and myfunc failing too with undefined object, as if it wouldn't be registered, but it is. It works just fine when I call var and function INSIDE the waitForElement, not passed as params. Do you know any reason that could be causing this? –  Jun 11 '21 at 06:00
  • I just have to say this sweet and simple and sob I shoulda thought of it. – Robert Ruby II Jan 07 '23 at 20:03
58

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);
Valen
  • 1,693
  • 1
  • 20
  • 17
  • For me, the `async` version has the advantage that you can adapt it to wait for multiple variables or other conditions before running your code. – JRI Jan 03 '20 at 18:45
  • 1
    you can do exactly that with this if you combine it with the async strategy above, thing is the defineProperty strategy has no polling involved and this is a huge plus. I was already doing the async way and wanted a more close to the js engine way of doing it and this nails it. – Emmanuel Mahuni Aug 01 '20 at 11:23
  • @JRI you CAN make you wait for multiple variables with `Object.defineProperty` if all of the variables are encapsulated in certain object, just set a local variable `count = `, `-1` each time and run the callback when count reaches `0`, good side of it is that it's instantaneous. I've encountered similar problems recently (wait for multiple onload) and you could write helper functions to reduce boilerplate. – Valen Aug 01 '20 at 14:01
  • Does that `, 1000` cause the `variable is defined` to happen a bit later than necessary? Would a `, 1` be better? – cherouvim Mar 28 '23 at 11:05
  • @cherouvim yea you are correct, but that would increase the burden on performance as well, it could even stall your whole program because javascript is single-threaded – Valen Jul 08 '23 at 14:13
25

I would prefer this code:

function checkVariable() {

   if (variableLoaded == true) {
       // Here is your next action
   }
 }

 setTimeout(checkVariable, 1000);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Tushar Ahirrao
  • 12,669
  • 17
  • 64
  • 96
  • 4
    I combined this with `typeof someVariable !== 'undefined'`, and also i had to use info from here: http://stackoverflow.com/questions/1055767/why-can-i-not-define-functions-in-jquerys-document-ready – JackLeo Sep 06 '11 at 09:25
  • 7
    this is not a good solution, since load of any proccess or dom can take more than 1000ms, then this breaks the flow. – Ninja Coding Aug 24 '18 at 18:03
16

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.

Cymen
  • 14,079
  • 4
  • 52
  • 72
12

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...

Toprak
  • 559
  • 6
  • 10
  • some tedious code in the `setTimeout()` section, couldn've done: `setTimeout(resolve, timer)` in `__delay__` remember `resolve` itself is a callable and `setTimeout()` only need a callable for the first argument, doesn't have to wrap `resolve` one more time. complete code: `function delay(timer=2000){ return new Promise(resolve => setTimeout(resolve, timer)); }` clean. – Valen May 20 '19 at 07:31
  • [as if in my answer](https://stackoverflow.com/questions/7307983#56216283) – Valen Jun 02 '19 at 23:29
  • Typescript simplified version of your delay method: export const usleep = (ms=1000) => new Promise(resolve => setTimeout(() => resolve(), ms)) – Joel Teply Jan 04 '22 at 23:14
3

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

  });
Ollie H-M
  • 485
  • 5
  • 17
3

You can use this:

var refreshIntervalId = null;
refreshIntervalId = setInterval(checkIfVariableIsSet, 1000);

var checkIfVariableIsSet = function()
{
    if(typeof someVariable !== 'undefined'){
        $('a.play').trigger("click");
        clearInterval(refreshIntervalId);
    }
};
2

Shorter way:

   var queue = function (args){
      typeof variableToCheck !== "undefined"? doSomething(args) : setTimeout(function () {queue(args)}, 2000);
};

You can also pass arguments

tdmartin
  • 251
  • 1
  • 8
2

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.

Jamie Dixon
  • 53,019
  • 19
  • 125
  • 162
  • 4
    This will work, unless the existence of someVariable is the result of an ajax call. Ajax calls may be (probably will be) completed after the call to ready. – dnuttle Sep 05 '11 at 12:27
  • Nope, that variable exists only after some time. I use `(window).load` because I saw a post here that it is being loaded _after_ `(document).ready`. Latter for me is better in this case. – JackLeo Sep 05 '11 at 12:29
  • Yep. Given that scenario he could fire the JS events from his success handler in the ajax request. Afaik this can be done from Flash to JS. – Jamie Dixon Sep 05 '11 at 12:29
  • This does not work if the script in question and the script that defines the variable both use $(document).ready and the script that defines the variable is included after the script that uses it. – kloddant Apr 26 '17 at 14:33
  • Document ready is fired after the DOM has been downloaded, not media. If you need to wait for media to be downloaded, use window load. – Daniel Dewhurst Apr 01 '19 at 09:45
1

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

Wallace Sidhrée
  • 11,221
  • 6
  • 47
  • 58
1
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.

Przemek
  • 3,855
  • 2
  • 25
  • 33
1

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();
0

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")

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Milimetric
  • 13,411
  • 4
  • 44
  • 56
0

I know I'm a few years late to this question: A lot of people have gone the timeout route, which if you do not have access to the code that defines the variable, that is probably the easiest way to go.

However, the OP suggests that it's all their own code. In this instance, instead of literally waiting for the window object to define the variable, I think it would be better to dispatch an event once the variable is defined, to not waste resources unnecessarily

Defining code

window.someVariable = 'foo';
const ev = new Event( 'someVariableDefined' );
document.dispatchEvent( ev );

Listening code

const triggerClick = () => {
    // cleanup
    document.removeEventListener( 'someVariableDefined', triggerClick, false );
    $('a.play').trigger("click");
}

if( !!window.someVariable ){
    triggerClick();
} else {
    document.addEventListener( 'someVariableDefined', triggerClick, false );
}
Dave Maison
  • 383
  • 1
  • 13
-1
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.

  • 1
    `typeof` always returns a string, and `void 0` always returns the literal value `undefined`, which will never be equivalent. Additionally, `void 0` is a trick used in tersers/uglifiers to decrease character count; you should never write it out yourself. Finally, this code is synchronous, so it will block the main thread until it completes. There are already multiple other correct approaches in this thread, use one of them instead. – superhawk610 Mar 25 '21 at 21:36