6

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?

FZs
  • 16,581
  • 13
  • 41
  • 50
rpivovar
  • 3,150
  • 13
  • 41
  • 79
  • 1
    You can use `set` method to handle setting the setting, and in that method call a callback function, which tells you that this setting has just been set. You could even use event listeners to do this and then it would be something like addEventListener('setting-was-set', setting => /* use setting here */) https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set https://developer.mozilla.org/en-US/docs/Web/API/EventListener – George Sep 10 '19 at 22:55
  • 2
    Yes, there's almost always a better way than polling with timeouts for the value to appear. However, please tell us more about "*`this.settings.options` is set somewhere else, and depending on the environment, it may be `undefined`.*". Where exactly is "somewhere else", is it still in the same unit of code, and when does the setting happen? Or when does the environment decide that the option stays `undefined`? – Bergi Sep 10 '19 at 23:14
  • sometimes this code operates in a local environment, and other times it's working with a receiver unit. There is a possible delay when the receiver unit is involved. In this particular scenario, the only place I can query for this data is whatever is inside `this.settings.options`, so there's not a workaround other than waiting for that property to be set (not sure if that's what you're looking for) – rpivovar Sep 10 '19 at 23:19
  • 1
    @rpivovar but your code knows whether there is a receiver unit or not, up-front? And it can call methods of that receiver unit? Maybe you can post the code of the receiver unit. – Bergi Sep 10 '19 at 23:47
  • 1
    This is an [XY Problem](http://xyproblem.info/) due to misunderstanding of Promises and asynchrony in general. Please paste your real program and explain what problem it is trying to solve. Show example inputs and outputs. See related: [_How do I return the response from an asynchronous call?_](https://stackoverflow.com/q/14220321) – Mulan Sep 11 '19 at 20:13

1 Answers1

1

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)
})
FZs
  • 16,581
  • 13
  • 41
  • 50