await user input with async/await syntax for clarity: moved here from stackoverflow after being pointed to Code Review being the better place for this question
I love async/await and Promises since I got my hands on them. And I might be overdoing it, but it feels like there should be a good and readable way to utilize async/await to get a little closer to a flow like, functionalISH programming style.
I would love to not have to only use async/await to wait for web resources to come back but also to wait for user input when I await it.
So far i have some code working similar to this shortened demo where I wrap a one time only EventListener into a Promise:


//// MAIN ///////

(async function(){

    //...do some async await stuff here... fetching stuff from a server
    // let services = await fetch(...) for example
    let services = [{url:"x",label:"1"},{url:"y",label:"2"},{url:"z",label:"3"}]


    let service_chosen = await showServicesToUserAndAwaitInput(services); 
    console.log("service_chosen:",service_chosen);
    // ... go on....
})()

//// END MAIN /////


async function showServicesToUserAndAwaitInput(services){
    
    if (services.length &lt; 1){return null}

    let choice = null;
    
    let serviceList = document.querySelector("#serviceList");
    
    // show list element
    serviceList.classList.remove("hidden") 

    // create some elements for the user to interact with
    for (let service of services){
        let button = document.createElement("BUTTON");
        button.innerHTML = service.label;
        button.addEventListener("click",function(){
          document.dispatchEvent(
            new CustomEvent('serviceChosen', { detail:service })
          )
        });
        serviceList.appendChild(button);
    }
    
    // returns promise with one time only event listener
    return new Promise((resolve,reject)=&gt;{ 
        document.addEventListener("serviceChosen",function(e){
            serviceList.classList.add("hidden") // hide again for we are done
            resolve(e.detail)
        },{ once: true })
    })
}
.hidden{
  visibility: hidden
}
&lt;div id="serviceList" class="hidden"&gt;

&lt;/div&gt;



But something about this use of the EventListener bugs me. Also: I use a promise that always resolves, which also seems strange.
On the upside: I get to read the code top to bottom and can follow the user flow within the MAIN without having to chase down events, callbacks and so on.
Yet, it feels like I am reinventing something somebody else might have already normed. So:
Is there a better way to achieve this? Are there best practices to work with user interactions or other DOM events in a async and/or thenable way?
First of all, I would not use Promises to do event based programming.
It is not used like that, so your code will get harder to follow.
Also, I would advise you to split up your functions more and omit some comments that way.


//// MAIN ///////

let services = [
    { url: "x", label: "1" },
    { url: "y", label: "2" },
    { url: "z", label: "3" },
];

showServicesToUser(services);

//// END MAIN /////

const serviceList = document.querySelector("#serviceList");

function createButtonFromService(service) {
    let button = document.createElement("BUTTON");
    button.innerHTML = service.label;
    button.addEventListener("click", function () {
        document.dispatchEvent(
            new CustomEvent("serviceChosen", { detail: service })
        );
    });
    serviceList.appendChild(button);
}

function showServicesToUser(services) {
    if (services.length &lt; 1) return;

    serviceList.classList.remove("hidden");

    for (let service of services) {
        createButtonFromService(service);
    }
}

function chooseService(service) {
    console.log("service_chosen:", service);
    // ... go on....
}

document.addEventListener("serviceChosen", function (e) {
    serviceList.classList.add("hidden"); // hide again for we are done
    chooseService(e.detail);
}, { once: true });
.hidden{
  visibility: hidden
}
&lt;div id="serviceList" class="hidden"&gt;

&lt;/div&gt;



My proposed code above is written in a scripted kind of way, with a global variable (serviceList), but you could also write this all in a class (especially if you have more code), to make it more readable and reusable.


class ServiceChooser {
    serviceListSelector;
    serviceList;
    services;

    constructor(serviceListSelector, services) {
        this.serviceListSelector = serviceListSelector;
        this.services = services;

        this.serviceList = document.querySelector(serviceListSelector);
        
        document.addEventListener(`serviceChosen${this.serviceListSelector}`, (e) =&gt; this.chooseService(e.detail), { once: true });
    }

    createButtonFromService(service) {
        let button = document.createElement("BUTTON");

        button.innerHTML = service.label;
        button.addEventListener("click", () =&gt; {
            const event = new CustomEvent(`serviceChosen${this.serviceListSelector}`, { detail: service });
            document.dispatchEvent(event);
        });

        this.serviceList.appendChild(button);
    }

    chooseService(service) {
        this.serviceList.classList.add("hidden");
        console.log("service_chosen:", service);
        // ... go on....
    }
    
    showServicesToUser() {
        if (this.services.length &lt; 1) return;

        this.serviceList.classList.remove("hidden");

        for (let service of this.services) {
            this.createButtonFromService(service);
        }
    }
}

//// MAIN ///////

let services = [
    { url: "x", label: "1" },
    { url: "y", label: "2" },
    { url: "z", label: "3" },
];

const sc = new ServiceChooser('#serviceList', services);
sc.showServicesToUser();

// now you could add 2 service choosers, sperate from each other
const sc2 = new ServiceChooser('#serviceList2', services);
sc2.showServicesToUser();

//// END MAIN /////
.hidden{
  visibility: hidden
}
&lt;div id="serviceList" class="hidden"&gt;

&lt;/div&gt;
&lt;div id="serviceList2" class="hidden"&gt;

&lt;/div&gt;