Using async functions to await user input from onclick
Asked Answered
G

2

15

I am new to async and maybe just am not wrapping my head around the fundamentals but I am trying to wait for user input from an onclick by calling an async function that pops a modal and waits for the user to submit data. After finding only one or two sources that even mentioned using async for waiting on page events that were not particularly helpful to my specific task... I came up with this:

asnyc func1 (){
  var userInput = await async2();

  //do stuff with user input
}
async func2(){
  //build modal content specific to task
  //display modal
  return new Promise(function(resolve,reject){
       $(document).on('click', '#save-input', function(e){
          var input = $('#input-data').val();
          resolve(input);
      });
  });
}

Everything seems to call correctly and i get the user input, but func1 never continues past the call to async2. So obviously im missing some crucial aspect of this but I can't seem to pull it from my sources.

Callback is not an option and there is a lot more to the code here than I can detail in short but the described above is the baseline functionality I need to perform.

Gyrfalcon answered 17/7, 2018 at 6:33 Comment(5)
Looks fine to me. Can you provide a minimal reproducible example closer to your actual code?Beeves
Notice you should use once instead of on, as the promise can resolve only once and it should automatically remove the handler after that.Beeves
I'll try to add one ASAP, would it make any difference with both functions being anonymous object member functions? And/or would it make a difference if i am calling two other functions before var input = happens? Wondering if i need to put await on each of those tooGyrfalcon
Issue resolved, I realized another portion of code was calling the func2 ahead of time and causing the promise to return in the wrong situation messing up the process flow such that when I actually wanted to wait for the promise...it had already been resolved.Gyrfalcon
You might want to delete your question in that caseBeeves
R
6

Simple snippet

const timeout = async ms => new Promise(res => setTimeout(res, ms));
let next = false; // this is to be changed on user input

async function waitUserInput() {
    while (next === false) await timeout(50); // pauses script
    next = false; // reset var
}

Example usage with jQuery:

// just change the value of `next` for the script to continue
$('#user-input').click(() => next = true);

async function myFunc() {
    // do stuff before
    await waitUserInput(); // wait until user clicks
    // do stuff after
}

myFunc() // launch function and start waiting for user input

Please see this working DEMO

// this is an async timeout util
const timeout = async ms => new Promise(res => setTimeout(res, ms));

let next = false; // this is to be changed on user input
let n = 1;

async function waitUserInput() {
    while (next === false) await timeout(50); // pause script but avoid browser to freeze ;)
    next = false; // reset var
}

async function myFunc() {
    $('#text').append(`* waiting user input...<br>`)
    await waitUserInput();
    $('#text').append(`* user has clicked ${n++} time(s)<br>`)
    myFunc()
}

$('#user-input').click(() => next = true)

myFunc()
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button id='user-input' style='padding:15px;color:white; background: tomato; border: 0; border-radius:8px; font-weight: bold'>CLICK ME !</button>
<div id='text'>
</div>

Further improvements

This example could easily be improved to your needs. For example the variable next could also be used to store the user input value and the waitUserInput returns that value. That will lead to writing things like:

const userResponse = await waitUserInput(); // get user input value

// this is an async timeout util
const timeout = async ms => new Promise(res => setTimeout(res, ms));

let next = false; // this is to be changed on user input

async function waitUserInput() {
    while (next === false) await timeout(50); // pause script but avoid browser to freeze ;)
    const userInputVal = next;
    next = false; // reset var
    return userInputVal;
}

async function myFunc() {
    $('#text').append(`* waiting user input...<br>`)
    const userResponse = await waitUserInput();
    $('#text').append(`* user choice is ${userResponse}<br>`)
    myFunc()
}

$('#user-input').click(function() { next = $('#text-input').val() })

myFunc()
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input id='text-input' type='text'/> 
<button id='user-input' style='padding:15px;color:white; background: tomato; border: 0; border-radius:8px; font-weight: bold'>SUBMIT !</button>
<div id='text'>
</div>
Rightly answered 21/2, 2019 at 15:49 Comment(1)
This only allows the user to click the button twice. What if you want the button to work always whenever it's clicked? See this jsfiddle example for the answer.Fingerboard
C
6

I searched a lot but found nothing about it, just this question. And I wanted something asynchronous without relying on loops to check, so I did a different solution.

The main problem was solving a promisse with an event listener, so I made the eventListener function inside the promisse and I use an arrow function to get the context of the promisse. Then I resolve the promise and cancel the eventListener after the first click.

And to make a function reusable with any listener I put an entry for that too.

function waitListener(element, listenerName) {
    return new Promise(function (resolve, reject) {
        var listener = event => {
            element.removeEventListener(listenerName, listener);
            resolve(event);
        };
        element.addEventListener(listenerName, listener);
    });
}

It will return the event if you want to use it for something. This makes it easier for you to write some sequence of codes in an orderly way. But to work it has to be inside an asynchronous function, which means that this function will start to run in parallel, so it is important to know where you are going to start in order not to hinder your process.

A simple use within some asynchronous function:

var element = document.querySelector("button");
await waitListener(element,"click");

A full sample:

function waitListener(Element, ListenerName) {
    return new Promise(function (resolve, reject) {
        var listener = event => {
            Element.removeEventListener(ListenerName, listener);
            resolve(event);
        };
        Element.addEventListener(ListenerName, listener);
    });
}


async function awaitClicks(){

var element = document.querySelector("button");


await waitListener(element,"click")
// Do thing 1
.then(e=>{
    console.log("e.clientX: "+e.clientX);
});
console.log(1);


await waitListener(element,"click");
// Do thing 2
console.log(2);


await waitListener(element,"click");
// Do thing 3
console.log(3);

    
}
awaitClicks();
<button>click</button>

If you need the same thing to happen every time you click a button, it is preferable to use just one addEventListener. And depending on what you are going to do you can use an addEventListener and cancel it already in the function it does using the removeEventListener. Now if you want to do something that depends on an order of events, maybe using this function can be a good solution.

Chekiang answered 3/9, 2020 at 7:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.