0

I am load HTML (external app) into an iFrame

I want to "do" something (callback) when an element becomes available in my iFrame. Here how I wrote it, and I'd like to write this with Promises instead:

function doWhenAvailable(selector, callback) {
  console.warn("doWhenAvailable", selector)
  if ($('#myiFrame').contents().find(selector).length) {
      var elt = $('#myiFrame').contents().find(selector);
      console.info("doWhenAvailable Found", elt)
      callback && callback(elt);
  } else {
      setTimeout(function() {
          doWhenAvailable(selector, callback);
      }, 1000);
  }
}

Actually instead of using setTimeout, I'd like to use setInterval to repeat the "find element" until it's found and resolve the "promise".

zabumba
  • 12,172
  • 16
  • 72
  • 129
  • I don't see how promises is especially relevant to this. I'd suggest using a MutationObserver instead if you want to improve the logic, but note that they won't work in IE10 or lower. Alternatively you could flip the logic so that instead of the parent window periodically checking the content of the child, you could make the child inform the parent once when the element is available. – Rory McCrossan Mar 08 '17 at 13:56
  • No, `setInterval` does not play nicely with promises – Bergi Mar 08 '17 at 14:04
  • to make the child inform the parent, I'd need to add code to the child, which I can't. The reason I ask this question is to understand Promises better. – zabumba Mar 08 '17 at 14:05

4 Answers4

2

No, you would not use setInterval, you just would wrap the timeout in a promise and drop the callback:

function wait(t) {
    return new Promise(function(resolve) {
        setTimeout(resolve, t);
    });
}
function whenAvailable(selector) {
    var elt = $('#myiFrame').contents().find(selector);
    if (elt.length)
        return Promise.resolve(elt);
    else
        return wait(1000).then(function() {
            return whenAvailable(selector);
        });
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • I see, the `return Promise.resolve(elt);` will allow the syntax `whenAvailable("dummy").then(...)`. Excellent. So on the `wait` Promise, `resolve` can be a function? I don't get why we don't have a `resolve()` call, is it implicit? What about the reject? can be optional? Sorry lot of questions, but I think I am almost getting it – zabumba Mar 08 '17 at 14:18
  • 1
    @zabumba Yes, `resolve` is a function, and it's directly called by the timeout. One could also have written `setTimeout(function() { resolve(); }, t)`. And no, we don't need `reject`, as we never want to error out. – Bergi Mar 08 '17 at 14:22
  • BTW what if the element never becomes available? I was confronted to that. That's why I though of the idea of using the `id=setInterval(...)` that I could then clear `clearInterval(id)`. That could work with @pedromss proposition. Nevermind. I understand Promises a lot better now. Cheers! – zabumba Mar 08 '17 at 14:37
  • 1
    When the element never becomes available, the promise never resolves and your callbacks wait forever. If you want to use a timeout (max number of attempts) or manually cancel the search, you can accept a second parameter that specifies how and then use `reject`. – Bergi Mar 08 '17 at 15:07
1

Keeping your recursive style, it would have become something like that :

function doWhenAvailable(selector) {
  var dfd = jQuery.Deferred();
  console.warn("doWhenAvailable", selector)
  if ($('#myiFrame').contents().find(selector).length) {
      var elt = $('#myiFrame').contents().find(selector);
      console.info("doWhenAvailable Found", elt)
      return dfd.resolve(elt);
  } else {
      setTimeout(function() {
          doWhenAvailable(selector).then(function(e) {
            dfd.resolve(e);
          });
      }, config[env].wrapper.timeOutInMs);
  }
  return dfd.promise();
}

But I would have tried to avoid recursive calls here

bviale
  • 5,245
  • 3
  • 28
  • 48
  • This code stacks the promises, I'd rather create another function and avoid creating a new promise for each setTimeout – bviale Mar 08 '17 at 14:07
  • There is nothing wrong with stacking promises (though you should avoid the [deferred antipattern](http://stackoverflow.com/q/23803743/1048572?What-is-the-promise-construction-antipattern-and-how-to-avoid-it)!), creating a promise for every single asynchronous unit is exactly how they were designed to be used – Bergi Mar 08 '17 at 14:11
1

The general idea is to return a promise instead of receiving a callback.

Example:

var xpto = function(res) {
    return new Promise((resolve, reject) => {
        if(res > 0) resolve('Is greater');
        else reject(new Error('is lower'));
    });
}

So in your case:

function doWhenAvailable(selector) {

  function work(callback) {
     if ($('#myiFrame').contents().find(selector).length) {
       var elt = $('#myiFrame').contents().find(selector);
       console.info("doWhenAvailable Found", elt)
       callback(elt);
    }
  }

  return new Promise((resolve, reject) => {
    console.warn("doWhenAvailable", selector)
    setInterval(() => work(resolve), 1000);
  })
}
pedromss
  • 2,443
  • 18
  • 24
0

Here:

function doWhenAvailable(selector) {
    return new Promise(function(resolve, reject){

console.warn("doWhenAvailable", selector)
  if ($('#myiFrame').contents().find(selector).length) {
      var elt = $('#myiFrame').contents().find(selector);
      console.info("doWhenAvailable Found", elt)
      resolve(elt);
  } else {
      setTimeout(function() {
          doWhenAvailable(selector).then(function(data){
  resolve(data);
});
      }, config[env].wrapper.timeOutInMs);
  }

    }
}

And call your function like that:

doWhenAvailable("#elemId").then(function(elt){
//do what you want
});
Charly berthet
  • 1,178
  • 5
  • 15
  • 31
  • Avoid the [`Promise` constructor antipattern](http://stackoverflow.com/q/23803743/1048572?What-is-the-promise-construction-antipattern-and-how-to-avoid-it)! – Bergi Mar 08 '17 at 14:12