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¶s=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.
Related
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.
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());
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.
i was hoping somebody could help me understand where i am going wrong, i have a basic bit of JavaScript; when a user clicks a button the inner HTML of a div is replaced to loading Gif while the rest of the functions calculate a final value and then the loading gif should be replaced with the final value. However what actually happens is the entire process is completed before any changes are made, so the loading gif is set and replaced with the final value instantly at the end. It works fine in firefox but not in chrome or IE. I need the loading giff to be set immediately so the user can see the value is being calculated as it can take some time.
const loadingGif = "path to giff";
$('#btn1').click(function() {
buttonClick()
});
function buttonClick(){
setLoadingGiff("buttonValue")
};
async function setLoadingGiff(range){
$('#divID').html(loadingGif);
var result = await calcFunction(range);
}
function calcFunction(){
//Do calculations ....
//finally
const finalCalulcation = "value";
$('#divID').html(finalCalulcation);
}
I want to create a function that when called would display a "Loading..." message, and display the results as soon as it finishes. when I do it like this:
function load() {
$('#status').html("loading...");
/* do something */
...
$('#status').html("done");
$('results').html(result);
}
The "loading" message never appears, after a while what a user sees is just the "done" message and the results. How can I make the "loading" text appear just the moment I want it to?
If "do something" is synchronous, the browser never gets a chance to update the UI between the two content changes.
To have the changes appear you need to do something like:
$('#status').html('loading...');
setTimeout(function() {
// do something
$('#status').html('done');
}, 0);
The setTimeout call gives the UI a chance to update the display before jumping into your "something".
Note that if possible you should try to ensure that "something" doesn't tie up your browser for a long time. Try to break the task up into multiple chunks, where each chunk is also dispatched using setTimeout().
Hard to tell without seeing the "stuff" part, but I hope this helps a little;
function load() {
$('#status').html("loading...");
function onLoaded(result) {
$('#status').html("done");
$('results').html(result);
}
// do your stuff here
// Not being able to see the "stuff", I guess you do some AJAX or something
// else which is asynchronous? if you do, at the end of your callback, add
// onLoaded(result)
}
The key is in the "do something". It depends on what you want to do but I would expect that you want to use jQuery's load() function.
Many jQuery functions accept 'callback functions' which are executed after the task is complete. The callback function section of the load() documentation should explain everything.