Search code examples
javascriptecmascript-6async-awaites6-promise

Wait for value to be defined without setTimeout?


I'm grabbing a value from somewhere and wanting to use it locally (using lodash):

const setting = _.get(this.settings, 'options');

this.settings.options is set somewhere else, and depending on the environment, this.settings.options may be undefined. In that case, I can do something like:

const set = (setting) => {
  ...
  ...
};

const getThing = () => {
  setTimeout(() => {
    const setting = _.get(this.settings, 'options');
    return setting ? set(setting) : getThing();
  }, 1000);
};

getThing();

This uses a setTimeout function to wait 1 second for this.settings.options to be set in that time, and if it's not yet set, the setTimeout function calls itself to check one second later. After that, it continues to the set() function with the acquired data. This seems to work every time, but it would be really nice if I could keep checking for this value until it is defined without a timer. I'm not sure if that's possible?

I've been trying to implement either a promise or use async / await to do this, but the examples I've seen also seem to use setTimeouts, and the solution ends up seemingly more complicated. Among others, I've mainly been looking at this answer. Regardless, I'm having trouble doing this with async / await and will have to continue troubleshooting that.

Is there a way to wait for this value to be defined without using a setTimeout or setInterval?


Solution

  • Unfortunately, there is no straightforward way to do it.

    If you can mutate this.settings (and this.settings.options is at least configurable), then you can define a setter, and wait for it to be triggered.

    This is generally an ugly pattern (use it only if there's no other way), but still better than periodically checking the existence of a property.

    Also note that this will work only if the mysterious code that sets .options uses [[Set]] (i.e., obj.prop = value or Reflect.set), and not [[Define]] (i.e., Object.defineProperty).

    if(typeof this.settings.options==='undefined'){
      //Define a setter
      Object.defineProperty(this.settings, 'options', {
        set: (value)=>{
          //Remove setter, and transform to simple property
          Object.defineProperty(this.settings, 'options', {value})
          //Hypothetical function to be called with the result
          doSomethingWithTheValue(value)
        },
        configurable: true,
        enumerable: true
      })
    }else{
      doSomethingWithTheValue(this.settings.options)
    }
    

    And, you can now easily rewrite this to a general function that uses Promises (and therefore supports async/await):

    function getAsync(obj, prop){
      return new Promise((resolve,reject)=>{
        if(typeof obj[prop]==='undefined'){
          Object.defineProperty(obj, prop, {
            set: (value)=>{
              Object.defineProperty(obj, prop, {value})
              resolve(value)
            },
            configurable: true,
            enumerable: true
          })
        }else{
          resolve(obj[prop])
        }
      })
    }
    
    getAsync(this.settings,'options')
      .then(doSomethingWithTheValue)
    
    //or, async/await:
    
    (async()=>{
      const value=await getAsync(this.settings,'options')
      doSomethingWithTheValue(value)
    })