How to use async/await on event handlers on buttons in JavaScript? - javascript

I'm new too JavaScript and am having trouble with async. I have a webpage with two buttons, If the yes button is pressed it will do some code and if the no button is pressed it will do different code but regardless of which one is pressed it will continue on in the function. For Example
function mainloop(){
document.getElementById("buttonyes").addEventListener("click", () => {
/* do some code */
})
document.getElementById("buttonno").addEventListener("click", () => {
/* do some different code */
})
/* wait for either button to be pressed and then continue with code */
console.log("yay waiting for stuff")
}
I believe the solution to this is promises and creating other functions to handle the buttons; however, tutorials I've seen only show one function solutions and if I understand correctly the EventListener when activated is using another function all to itself. I've come over from C and all this object properties asynchronization stuff is throwing me for a loop.
I'd really love a way to keep it all in the same main function as this challenge specifically called for me to use async/await on this damned buttons.

An Event Handler is already a way of using asynchronous code. The code inside the handler already waits to be executed until somebody presses the button.
If you have some function that needs to be executed after both clicks you can simply add it to both event handlers. Btw you don't need to put the addEventListener in a loop! Just add them once.
document.getElementById("buttonyes").addEventListener("click", () => {
/* do some code */
myNewFunction()
})
document.getElementById("buttonno").addEventListener("click", () => {
/* do some different code */
myNewFunction()
})
/* wait for either button to be pressed and then continue with code */
function myNewFunction() {
console.log("yay waiting for stuff")
}
You only need another async await if the code in the buttons does something that needs to be awaited, for example fetching data from the internet. Or else we need a bit more context to know why you want to solve this problem with an extra async await.

I would advise you to use the async await with promise to handle this kind of scenario. eventHandler supports the async await under the hood.
Please check this example:-
document.querySelector('#btn')
.addEventListener('click', handleClick)
async function handleClick() {
await new Promise((resolve) =>
setTimeout(resolve, 5000))
alert('clicked')
}

Related

What is the proper way to use async/await to receive button presses without blocking in a while loop?

I'm designing a simple browser based version of a popular game and I'm having trouble understanding how promises should be created by a button .onclick handler to deal with user input only at specific times. Here's the basic game logic:
<button id="button1">1</button>
<button id="button2">2</button>
<script>
var Game = {
ready_for_input: false,
game_over: false,
play_round: async function () {
//do some setup, check for a few initial win conditions, then......
if (round_not_yet_won) {
ready_for_input=true;
while (round_not_yet_won) {
await (//somehow get a promise that is fulfilled on button click from one of two buttons)
//when promise fulfilled by click, do some additional condition checking
if (win) {round_not_yet_won=false; ready_for_input=false; return;} else {//go back to while loop}
}
}
}
while (!Game.game_over){
Game.play_round();
}
//get buttons by id and addEventListener('click', handle_button_x())
function handle_button_x() {//create a promise maybe?}
</script>
I realized while debugging previously that the reason things weren't flowing logically was that I hadn't used async/await on the while loop, so it was sporadically hanging on certain initial conditions and not letting the page send the click event. Now i realize i need to let play_round() await the button click and stop blocking so the page can do its thing, but i'm not understanding how I link up the promise both to the play_round() await structure and the handle_button_x() handler. It seems wrong to my brain to do something like this, e.g.:
await handle_button_x();
because this will return a promise, but it seemingly won't be linked to a button press event call of the same function. Does my failure of understanding lie in how Promise.resolve() is called maybe?
Thanks for any help.

How to pause javascript code execution until fetch() actually gets the file

Let's say the standard progression on the page kickstarts with the window load event like this,
window.addEventListener("load",function(){ doTheseFirst(); }, { once: true });
function doTheseFirst()
{
setTimeout(function(){ andThenDoThese(); }, 5000);
// Maybe show some pictures and play some sounds for 5 seconds
}
function andThenDoThese()
{
// Show other pictures and play other sounds until user clicks OK
elementOnPageForOK.addEventListener("click", andThenDoTheseTooWhenUserClicksOK);
}
function andThenDoTheseTooWhenUserClicksOK()
{
// etc etc
// You get the idea
}
Let's say this piece of code is used over and over again.
But on certain occasions the user has to see a notification before the execution chain gets going. An easy solution for that is using an alert box since it blocks/pauses the code execution. The following code makes the alert box pop up as soon as fetch() gets the content from the txt file. Note that fetch() and window load are working independently.
fetch("somefolder/notification.txt").then(function(response){return response.text();}).then(function(contentOfTheTxtFile){ alert(contentOfTheTxtFile); });
This works because when the alert box is closed, the main code goes back to its normal flow just as needed.
But how do I do the same thing without using a crude alert box that I cannot assign any styles to?
Can I make it so that the code execution is paused as soon as fetch() gets the file and then it is unpaused immediately once the user clicks something like an OK or NEXT button?
Is this possible with async/await?
EDIT1: Further clarification
Please remember that it is uncertain whether fetch() or window load will fire the completion signal first. Because download speed is totally unpredictable.
So if we set them both like this, we don't know just how many milliseconds would pass for the other to catch up.
if(thisTimeItIsASpecialCase){
fetch("somefolder/notification.txt").then(function(response){return response.text();}).then(function(contentOfTheTxtFile){ /*What to do when the file is ready*/ });
}
window.addEventListener("load",function(){ doTheseFirst(); }, { once: true });
A simplified way to achieve the desired result: Put doTheseFirst() function on hold and make it wait for fetch() and let it finish its task of getting the file if fetch() is indeed trying to get a file. Also let doTheseFirst() go ahead and run in case there is no fetching to do.
You can do something like the following
You define some html where you want to display your content and hide it until your content is really available
You define a button to hide this element again
The next eventhandler is just there to call a Promise resolve callback which will be defined when needed
When your text is available to add it to your display and create await a new Promise which is resolved upon the click of the next button
The next handler in the promise chain hide the display again.
This code is of course very rudimentary and schematic. For real use you would encapsulate this in a own class or module. If you are using some framework like React, Angular, ... there are tons of components ready to use, which provide exactly this functionalty. For instance as modal dialog or whatsoever.
var resolver = undefined;
function next() {
if (resolver) resolver.call();
}
function getText() {
fetch("https://baconipsum.com/api/?type=meat-and-filler&paras=3&format=text")
.then(response => response.text())
.then(text => {
document.getElementById("thetext").innerText = text;
document.getElementById("display").hidden = false;
return new Promise(resolve => {
resolver = resolve;
});
})
.then(() => {
document.getElementById("display").hidden = true;
resolver = undefined;
})
.catch(e => console.log(e));
}
<input type="button" onclick="getText()" value="Get Text">
<div id="display" hidden>
<div id="thetext"></div>
<input type="button" onclick="next()" value="OK">
</div>
You can show the content of the text file, which you received by fetch, in a nice <div> or in any element in the fetch and you exit the function.
You add an EventListener to a button upon which when user clicks you can move to next action.

How to execute function after rendering except "setTimeout()"

Environment
Jquery2.0.3
What I want to do
(like chatapp messanger)
select image file and display as preview
↓
submit
↓
delete preview place
Code
async function send() {
sendMessage(); // send text(omit detail)
await sendImage(); // send image
resetAfterSending(); // delete preview display
}
function sendImage() {
return new Promise(res => {
messages.append(`
<div class=".preview-img">
<img src="${$('.hoge2').find('img').attr('src')}">
</div>
`);
return messages.ready(() => {
return res();
});
});
}
function resetAfterSending() {
/**
* if I use hide() instead of remove(), element remains in DOM element(you can see in chrome elements panel)
* But in this time, I need to delete in DOM element as well
*/
$('.preview-img').remove();
}
trouble
When I didn't use async/await, resetAfterSendingMessage() was executed and there was no .preview-img element in DOM, so I coudn't even send image data.
That's why I tried to use async/await like code at the top.
But when I tried to change like that ↓ ,image could be sent.
function send() {
sendMessage();
sendImage();
//**use setTimeout() !!
setTimeout(() => {
resetAfterSending();
},0);
}
I think async/await executes function, but not finish rendering. This causes problem. (I even don't know it's true or not)
If I need to execute function after rendering, I need to use setTimeout() as THIS LINK says?
If somebody knows the way instead of setTimeout(), plz comment it out here.You know setTimeout() is not kinda cool, hope you get what I mean.
Since sendImage() returns a Promise, you could use .then()
sendImage().then(() => resetAfterSending());

How to make Protractor's ElementArrayFinder 'each' function wait for the current action to complete before advancing to next iteration?

I am using protractor to do e2e tests in an Angular 8 app and I am currently having trouble making an 'each' loop work as I need.
In my page object I have created a method that returns an ElementArrayFinder.
public getCards(): ElementArrayFinder {
return element.all(
by.css(
`${this.baseSelector} .board-body cs-medication-card`,
),
);
}
For each of the elements returned I want to perform a set of actions that consists in clicking in a button that opens a menu list (while the menu is open there is an overlay element over all the view except the menu list) and pick one of the options. In my test I have this:
await page.board
.getCards()
.each(async (el: ElementFinder) => {
await until.invisibilityOf(await page.getOverlay());
await el
.element(by.css('.card-header .actions'))
.getWebElement()
.click();
await expect(page.isItemInMenu('X')).toBeTruthy();
await page
.getMenuItemByLabel('X')
.getWebElement()
.click();
});
I was expecting that for each card it would click the actions button, check if the option is in the list and click in the option.
What is happening is that it seems that protractor is trying to do everything at same time, since it says it cannot click on the actions button because the overlay is over it. The only way to the overlay be over the button is if the action in the previous iteration is not complete. I have already threw in the ExpectedCondition to wait for the overlay to be invisible but no luck. If only one element is returned by the ElementArrayFinder it does what is supposed.
I am out of ideas, any help would be much appreciated.
Protractor's each is asynchronous.
It's fine when you need to perform some action over an array of element (e.g get text or count) but it's not the best choice when you need to perform some kind of scenario.
I'm suggesting to use any other way like for loop.
Another workaround (which again might not work because of async nature of .each) is FIRST to wait for overlay to appear and the wait for it to disappear.
The each function is almost of no use. It even doesn't wait for the promise returned by each Function.
await $$('...').each((ele, index) => {
return ele.$('.fake_class').click().then(() => {
console.log(`the item ${index} is clicked.`)
return browser.wait(5000)
});
});
I think there are some issues in the function of each.
getWebElement() is unnecessary
await expect(page.isItemInMenu('X')).toBeTruthy(); seems wrong
await until.invisibilityOf(await page.getOverlay()); I'm not sure it work well.
Please try following code:
await page.board
.getCards()
.each(async (el: ElementFinder) => {
// await until.invisibilityOf(await page.getOverlay());
browser.sleep(10*1000)
await el
.element(by.css('.card-header .actions'))
.click();
// expect(await page.isItemInMenu('X')).toBeTruthy();
await page
.getMenuItemByLabel('X')
.click();
})
.catch((err)->{ // use catch() to capture any runtime error in previous each
console.log('err: ' + err)
})
If above code worked, you can revert back the expect and run again, then remove browser.sleep() and revert back your await until.invisibilityOf(....) and run again.

How to let JavaScript wait until certain event happens?

I am writing a webpage with the following structure:
One section (table A) depends on another section (table B);
Another section (table B) has elements that require recalculation on each update. The calculation is handled by external tools, and will cause an event when finished.
In order to guarantee correctness, the table need to be updated only after the other table is fully updated (i.e., done with computation). However, I don't know how to effectively achieve this, and I could not find any wait facility within JavaScript.
For now, I am using the following method:
Declare a global variable updated and make it false;
After the first table received input, I make an empty while loop until updated is true;
Add an listener, once the calculation is done and the event received, set updated to true.
This seems unintuitive to me but I cannot think of any other way of doing it. Is there any good ways to do this?
Thanks for any inputs!
In 2022, it's useful to have an event listener that fires off a Promise (which can be used in promise-chains, or async/await code). A clean way to make one:
function getPromiseFromEvent(item, event) {
return new Promise((resolve) => {
const listener = () => {
item.removeEventListener(event, listener);
resolve();
}
item.addEventListener(event, listener);
})
}
async function waitForButtonClick() {
const div = document.querySelector("div")
const button = document.querySelector("button")
div.innerText = "Waiting for you to press the button"
await getPromiseFromEvent(button, "click")
div.innerText = "The button was pressed!"
}
waitForButtonClick()
<button>ClickMe</button>
<div></div>
Add an listener, once the calculation is done and the event received, set updated to true.
Instead of setting updated to true, and then waiting for updated to be true- just do whatever you want to do in the listener.
myEventBus.addListener(function () {
// do whatever
updateTable();
alert('table updated!');
});
Doing empty while loops is a bad idea. Not only do you burn CPU cycles, but Javacript is single threaded so you will loop forever without giving anyone a chance to change the variable.
What you can do is rewrite the table that has other people depending on it to "fire an event itself". There are many ways to do this, but basicaly you just want it to call a "continuation' function instead of blindily returning. This function can be predefined or you can pass it as a parameter somewhere.
//this is just illustrative
//Your actual code will be probably very different from this.
function update_part(){
//do something
signal_finished_part()
}
var parts_done = 0;
function signal_finished_part(){
parts_done ++;
if(parts_done >= 5){
signal_all_parts_done();
}
}
function signal_all_parts_done()
{
//do something to table A
}
You could write a callback function for whatever triggers the update. To avoid messy callbacks, you could use promises too, and update parts of the table depending on the data retrieved in the update operation. Open to suggestions.

Categories