Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

javascript: waiting until property of an element is ready to continue

Tags:

javascript

I have a property of an element that is injected into my page from a 3rd party:

document.querySelector('#embed-container #mf2-events').jsMF2

the jsMF2 is said injected property. I need to wait until the jsMF2 property is defined in order to do work with it. Originally, I just set a timeout, but this is undesirable for many reasons. Is there a way I can spin until the property or set a callback? If this was a C program, I would write something like this:

    while (document.querySelector('#embed-container #mf2-events').jsMF2 === undefined ||
           document.querySelector('#embed-container #mf2-events').jsMF2 === null) { } 

    // do work
like image 857
user3689167 Avatar asked Sep 27 '22 03:09

user3689167


People also ask

How to wait until an element exists JavaScript?

To wait to do something until an element exists in the DOM, you can use JavaScript MutatationObserver API. As the name suggests, it can be used to listen out for changes in the DOM. You can then fire a (callback) function in response to this.

How do you wait for an element to appear?

If you need to wait for an element to appear, the async wait utilities allow you to wait for an assertion to be satisfied before proceeding. The wait utilities retry until the query passes or times out. The async methods return a Promise, so you must always use await or .then(done) when calling them.


1 Answers

You don't want a busy-wait, because then no other JavaScript can run (not to mention large parts of the browser UI), and so the property won't get defined.

Ideally, whatever's providing that property would have an event it fires that you can hook into. I'm going to assume that you've looked and not found one. :-)

Once support for the latest stuff in ECMAScript6 (aka "ES6") becomes widespread (it isn't at present), you might be able to use Proxy for this (provided your target browsers allowed Proxy on their HTML element instances). But sufficiently widespread support for Proxy will take a couple of years if not longer (and Proxy can't be shimmed/polyfilled). (In ES7, you could use Object.observe, but presumably Proxy, which is defined by the current [as of June 2015] standard, will be broadly-supported before an ES7 technology is.)

Until/unless you can use Proxy, a timer is indeed the correct way to handle this situation. It can be a really aggressive timer if necessary.

If the element is known to exist but you're waiting for the property:

check(function(element) {
    // It's there now, use it
    // x = element.jsMF2
});

function check(callback) {
    var element = document.querySelector('#embed-container #mf2-events');
    if (element && 'jsMF2' in element) {
        setTimeout(callback.bind(null, element), 0);
    } else {
        setTimeout(check.bind(null, callback), 0);
    }
}

Most browsers will fire that timer immediately when the JavaScript thread is available the first couple of times, then throttle it back to at least a 4ms delay for subsequent calls. Still pretty fast.

You don't have to be hyper-aggressive, though; humans are slow compared to computers, you could probably use 10, 20, or even 50ms.

If there's any chance that the property won't appear, you want to stop that repeated series of setTimeout eventually (after a second, after 10 seconds, after 30 seconds, after 60 seconds, whatever's appropriate to your use case). You can do that by remembering when you started, and then simply giving up rather than rescheduling if it's been too long:

var started = Date.now();

check(function(element) {
    // It's there now, use it
    // x = element.jsMF2
});

function check(callback) {
    var element = document.querySelector('#embed-container #mf2-events');
    if (element && 'jsMF2' in element) {
        setTimeout(callback.bind(null, element), 0);
    } else {
        if (Date.now() - started > 1000) { // 1000ms = one second
            // Fail with message
        } else {
            setTimeout(check.bind(null, callback), 0);
        }
    }
}

Side note: The query

var document.querySelector('#embed-container #mf2-events');

...is a bit odd. It says: Give me the first element with the id mf2-events found inside an element with the id embed-container. But id values must be unique on the page. So all that really says is "Get tme the #mfs-events element, but only if it's inside a #embed-container element."

Unless that's really what you meant, the dramatically faster

var document.getElementById('mf2-events');

...would be the way to go.

like image 195
T.J. Crowder Avatar answered Oct 03 '22 22:10

T.J. Crowder