Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using async functions to await user input from onclick

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.

like image 547
Aaron Rosano Avatar asked Jul 17 '18 06:07

Aaron Rosano


2 Answers

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.

like image 171
KevynTD Avatar answered Nov 14 '22 01:11

KevynTD


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>
like image 35
TOPKAT Avatar answered Nov 14 '22 00:11

TOPKAT