1

In JS, I would like to:

  1. Define a function that defines a promise.
  2. This function then has an event to resolve the promise.
  3. The function returns the promise without being resolved yet.
  4. When the promise is resolve the caller will receive the value of the resolve.

Something like this:

function my_func() {
    // This creates an empty promise.
    const p = Promise;

    // When a user clicks a button in UI then we resolve the promise.
    // This would be in an event!
    // p.resolve('user clicked OK');

    // Returns empty promise.
    return p;
}

function main() {
    // Here when the promise is resolved then we get the value.
    my_func().then(function(v) {
        console.log(v);
    });
}
user5507535
  • 1,580
  • 1
  • 18
  • 39
  • So just do it? `return new Promise(resolve => { okButton.onclick = (e) => resolve('user clicked ok'); })` – Bergi May 04 '21 at 16:10

2 Answers2

3

Found answers here: Creating a (ES6) promise without starting to resolve it

deferreds can be passed around as I want and then the promise can be resolved or rejected somewhere else.
Haven't tested this in code yet, so don't know if there's any problem with this approach.

function my_func() {
  const deferreds = [];
  const p = new Promise(function (resolve, reject) {
    deferreds.push({ resolve: resolve, reject: reject });
  });
  const btn = document.querySelector('.btn');

  btn.addEventListener('click', event => {
    deferreds[0].resolve('User clicked button!');
  }, {once: true});

  return p;
}

function main() {
  my_func().then(function (v) {
    console.log(v);
  });
}

main();
<button class="btn">
    Hi! Click me
</button>
user5507535
  • 1,580
  • 1
  • 18
  • 39
  • It would help if you could show your real code in the question. In the example `my_func` code in your answer, there is absolutely no reason to use a deferred object, you could (should!) just place the `addEventListener` call inside the `new Promise` executor. The approach with deferred objects should only be necessary if you have a collection of them, with some other code dynamically deciding *which* of them to resolve - that's why `deferreds` is an array in the linked answer. But in your case, it seems unncessary. – Bergi May 04 '21 at 17:08
  • Unfortunately my code is a bit more complex, it's an ExtJS window with a form inside it, I want to create a promise when the window is shown and assign it to the form as a property. When the user clicks on a button on the form it would hide the window and fulfill the promise with the form answers. I don't know why JS doesn't allow one to create an empty promise and do this kind of thing, seems much more simple and intuitive to me to be able to do it like this. – user5507535 May 04 '21 at 17:17
  • Mostly because an "empty promise" would need to be resolved from the outside - i.e. everyone who had access to the promise object could resolve it. That's quite error-prone and even a security issue, promises are strictly one-way communication. Even in your case, the function that shows the window should create the promise, install the event listener, and store the promise in the form object - [no need for deferreds](https://stackoverflow.com/a/32857145/1048572). Also the [deferred pattern is not exception-safe](https://stackoverflow.com/a/28692824/1048572). – Bergi May 04 '21 at 17:26
1

It seems like an odd design, but as Bergi comments, there's nothing stopping you from doing it -

function my_func () {
  return new Promise(r =>
    document.forms.myapp.mybutton.onclick = r
  )
}

function main () {
  my_func().then(event => console.log(event.target))
}

main()
<form id="myapp">
  <button type="button" name="mybutton">Click me</button>
</form>

A Promise can be resolved a maximum of one time, so it probably makes sense to automatically remove the event listener after the first click. We could rename my_func to onclick and give it a parameter to make it reusable -

function onclick(elem) {
  return new Promise(r =>
    elem.addEventListener("click", r, {once: true})
  )
}

function main () {
  onclick(document.forms.myapp.mybutton)
    .then(event => console.log(event.target))
    
  onclick(document.forms.myapp.otherbutton)
    .then(event => console.log(event.target))
}

main()
<form id="myapp">
  <button type="button" name="mybutton">A</button>
  <button type="button" name="otherbutton">B</button>
</form>

Now onclick can be used in more interesting ways. For example, we could make a button that needs two clicks before the effect is triggered -

function onclick(elem) {
  return new Promise(r =>
    elem.addEventListener("click", r, {once: true})
  )
}

function main () {
  onclick(document.forms.myapp.mybutton)
    .then(event => console.log(event.target))
    
  onclick(document.forms.myapp.otherbutton)
    .then(_ =>
      onclick(document.forms.myapp.otherbutton)
        .then(event => console.log(event.target))
    )
}

main()
<form id="myapp">
  <button type="button" name="mybutton">click once</button>
  <button type="button" name="otherbutton">click twice</button>
</form>
Mulan
  • 129,518
  • 31
  • 228
  • 259
  • I see nothing odd about it, all the four points in the OPs list are pretty standard usage for *every* function that returns a promise. – Bergi May 04 '21 at 16:34
  • OK, but I was looking for a way to return the promise without a function inside it because I would pass this promise to other place and then I would resolve it there. It seems to not be possible to do it though. – user5507535 May 04 '21 at 16:46
  • @user5507535 If some other place is responsible for resolving the promise, that other place should construct it. What is the actual problem you're trying to solve? – Bergi May 04 '21 at 16:47
  • +1 for using `{once: true}` – Bergi May 04 '21 at 16:47
  • my apologies if this came across the wrong way. I only mean it's a bit unconventional to use Promise instead of event listeners via `addEventListener` – Mulan May 04 '21 at 16:56