user3689167
user3689167

Reputation: 873

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

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

Upvotes: 6

Views: 6214

Answers (3)

T.J. Crowder
T.J. Crowder

Reputation: 1074385

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.

Upvotes: 4

Dennis C
Dennis C

Reputation: 24747

There were DOM Events which is not very popular and later replace it with a new standard MutationObserver.

But you should check if your target browser support it or implement both.


EDIT: You may also use Object.observe() if the property is not an dom attribute.

Upvotes: -1

wap300
wap300

Reputation: 2770

The only real way out here is to use a callback. How is the property set? If it is set by, say, ajax request, you need to add a function as the request parameter that will be run when the request is satisfied. The topics you will probably need are callbacks, asynchronous JavaScript and maybe promises, which are modern approach to async calla.

Upvotes: 0

Related Questions