3

I can do this to check if a global variable is defined or not:

if (window.globalVariableName) {
    // it's defined, now it's safe to work with it
}

Now I have a nested hierarchy, that I should work on only if every nested object is defined up to the leaf, and this nested hierarchy can be anything. For example I can have these two hierarchies:

human.head.mouth.lips.upperLip.color

building.firstFloor.hallway.lastRoom.chair.density

I want to check for existence of all the nesting, up to the leaf value, and only if the entire hierarchy including the leaf are defined, then do something. In other words I want to be able to dynamically create this static code:

if (window.human.head.mouth.lips.upperLip.color) {
   // now I know human is defined, and head, and mouth, and ... and up to color
}

I tried this code, but it's not working:

function checkThenRun(targetToWaitFor, callback) {
    if (typeof callback !== "function") {
        console.error('Callback for checkThenRun should be a function');
        return;
    }
    if (targetToWaitFor.indexOf('.') > -1) {
        targetToWaitFor.split('.').reduce(function (anchor, recursion) {
            if (anchor) {
                globalVariableForReducingInWaitAndRun = window[anchor];
                if (globalVariableForReducingInWaitAndRun) {
                    globalVariableForReducingInWaitAndRun = globalVariableForReducingInWaitAndRun[recursion];
                }
            }
            else {
                globalVariableForReducingInWaitAndRun = globalVariableForReducingInWaitAndRun[recursion];
            }
        });
        if (!globalVariableForReducingInWaitAndRun) {
            console.warn('checkThenRun waiting for ' + targetToWaitFor + '...');
            setTimeout(function () {
                checkThenRun(targetToWaitFor, callback);
            }, 500);
            return;
        }
    }
    if (typeof window[targetToWaitFor] === "undefined") {
        console.warn('checkThenRun waiting for ' + targetToWaitFor + '...');
        setTimeout(function () {
            checkThenRun(targetToWaitFor, callback);
        }, 50)
        return;
    }
    callback();
}

This is the usage sample:

checkAndRun('human.head.mouth.lips.upperLip.color', function() {
    // now I know this hierarchy is created, I can change color for example
});

Update:

I'm developing a system based on a mashup architecture. A web page is populated from many different sources. I can't and I don't have access to those external sources. I can't ask them to provide an API for me because they don't exist. It's another system that works, and injects something into the page but through asynchronous operation, thus I don't know when that operation is finished. But I can start my operation only after that. This is why I need to check the existence of the entire hierarchy.

  • 1
    Why the wait? You want to see if the leaf suddenly appears from somewhere? – mplungjan Mar 23 '20 at 10:13
  • I guess another system creates this hierarchy in the global space. Maybe another script. – Saeed Neamati Mar 23 '20 at 10:14
  • There are some problems with this code. `reduce` is not used correctly, you never return anything inside it, so `anchor` will always be `undefined`. And `globalVariableForReducingInWaitAndRun` is never declared (with `let` or `var`) is that intentional? I also think it's a bit too complex, it could be greatly simplified – blex Mar 23 '20 at 10:19
  • allthough it might be a valid question, i have to throw this in here: the problem you are trying to solve should've already been solved from your external source. meaning, why should `human` be defined, but not `head`? that's poor design. – malifa Mar 23 '20 at 10:19
  • I think https://stackoverflow.com/questions/16142957/undefined-error-when-checking-undefined-in-an-if-statement answer 1 is a good solution – Kuo-hsuan Hsu Mar 23 '20 at 10:22

3 Answers3

2

It seems you need to wait for some (external) script to add content to the global scope. Usually, there are ways to avoid that. Most libraries allow you to define callbacks when some (async) operation finishes. You should really check the documentation of the script/library that you are trying to work with. If there is absolutely no other way, you can use the following approach to wait for the variable to be truthy:

function waitUntilTruthy(cb) {
  return new Promise(resolve => {
    const interval = setInterval(() => {
      if (cb()) {
        clearInterval(interval);
        resolve();
      }
    }, 50);
  });
}

waitUntilTruthy(() => window?.human?.head?.mouth?.lips?.upperLip?.color).then(() => {
  // variable is defined 
});

Please not that the optional chaining syntax (a?.b?.c) will be in ECMAScript 2020 and might not yet work in your targeted runtimes (i.e. browsers and/or Node.js). Make sure to check for compatibility and consider using Babel or TypeScript 3.7+. Have a look at How to avoid 'cannot read property of undefined' errors? for alternatives to optional chaining.

str
  • 42,689
  • 17
  • 109
  • 127
1

Your code can be greatly simplified:

function checkThenRun(path, callback) {
  if (typeof callback !== "function") {
    console.error('Callback for checkThenRun should be a function');
    return;
  }
  if (getNestedProp(window, path)) {
    callback();
  } else {
    console.warn('checkThenRun waiting for ' + path + '...');
    setTimeout(function() {
      checkThenRun(path, callback);
    }, 500);
  }
}

function getNestedProp(obj, path) {
  const parts = path.split('.');
  let current = obj;
  for (let i = 0; i < parts.length; i++) {
    if (typeof current === 'object' && current !== null) {
      current = current[parts[i]];
    } else {
      return undefined;
    }
  }
  return current;
}

// Usage
checkThenRun('hello.world', () => { console.log('OK'); });

// For the demo
setTimeout(() => {window.hello = {world: 42}; }, 2500);
blex
  • 24,941
  • 5
  • 39
  • 72
0

I know it's stylistically dubious to use try/catch for things that aren't actually errors (from the program logic's point of view).... but here the alternatives seem even less clear, so:

try {
    temp=human.head.mouth.lips.upperLip.color;
}
catch(err) {
    temp=false;
}
if(temp){
    do_stuff(temp);
}

but not this (which hides errors in the function):

try {
    temp=human.head.mouth.lips.upperLip.color;
    do_stuff(temp);
}
catch(err) {
}
Roger Krueger
  • 283
  • 3
  • 8