I would like to change the content of a paragraph while the button handler is running rather than waiting for the button handler to finish. Here is my code:
Output: <p id="output"></p><br><br>
<input type="button" onclick="buttonPressed()" value="submit" id="button"><br><br>
<script>
function buttonPressed(){
console.log("output 2")
document.getElementById("output").innerHTML = "output 1"
while (true){
// doing something that takes a long time
}
}
console.log("output 1")
document.getElementById("output").innerHTML = "output 1"
</script>
upon pressing the button I would like the output to read "output 2" rather than having to wait for the button handler to finish. I have a while true in the button handler to demonstrate this, in reality I won't have a while true but rather some kind of process that takes a while. I would like to let the user know that this process has begun and that they should wait for it to finish.
Thanks for the help
The reason that your output is not showing is because the browser thread is busy with executing the current task (your function). As a result, the browser cannot redraw the page which would show the updated output.
In order to get around this, you need to use two separate tasks. One way to accomplish this would be to simply run your long running code in a timeout.
function buttonPressed(){
console.log("output 2")
writeOutput("output 2")
setTimeout(function(){ //use a separate task and allow the page to draw
while (true){
// doing something that takes a long time
}
},4);
}
As a side note, 4 is used here because 4 milliseconds is generally the smallest time you can use for one timeout due to browser limitations and the html5 spec
Related
UPDATE
Solved, sort of. The problem is the single-threaded nature of Javascript and my misunderstanding of its event model. The loop doesn't get interrupted by the timeout.
So, the solution is that instead of using a callback, I just grab a timestamp before starting the loop, and then each time through the loop compare against it. If more than X seconds have passed, I fire the confirm dialog:
let start = new Date();
...
while ( keepGoing && matchNotFound )
{
...
let end = new Date();
if ( end - start > 10000 )
{
keepGoing = confirm( "This run seems to be taking a while - keep going?" );
start = end;
}
}
Probably not the most efficient or idiomatic solution, but it works for my purposes.
ORIGINAL
I'm trying to call a function through setTimeout, but the callback never fires no matter what time interval I use. I don't know if the problem is in how I've set up the call, or if my understanding of how to use it is completely wrong.
I'm in the early stages of teaching myself Javascript, and have written a version of Dawkins' Weasel program as an exercise. Depending on the parameters the algorithm can take a long time to run, so my thought was to use setTimeout to call a function asking the user if they want to continue and give them the option to bail, sort of like so:
var keepGoing = true; // flag to continue main loop
var timeoutId; // stores result of setTimeout
...
/**
* Asks the user if they want to continue. If they do, reschedule this function to
* execute again after 5 seconds.
*/
function alertFunc()
{
keepGoing = confirm( "This is taking a while - do you want to continue?" );
if ( keepGoing )
{
timeoutId = setTimeout( alertFunc, 5000 );
}
}
/**
* Simulation loop
*/
function doWeasel()
{
/** simulation setup */
timeoutId = setTimeout( alertFunc, 5000 );
/** Loop until we find a match or the user gets bored */
while ( keepGoing && matchNotFound )
{
/** do the thing */
}
clearTimeout( timeoutId );
/** some cleanup */
}
That's the basic idea, anyway. The doWeasel function is tied to a button click in a form in the HTML document:
<html>
<head>
<!-- header stuff -->
</head>
<body>
<!-- long-winded explanation -->
<div class="formarea">
<form class="inputForm">
<!-- form stuff -->
<input type="button" value="OK" onclick="doWeasel()"/>
<input type="reset"/>
</form>
</div>
<div id="outputarea">
<h2>Output from the thing</h2>
<script type="text/javascript" src="weasel.js"></script> <!-- source for doWeasel() -->
</div>
</body>
</html>
The problem I'm having is that alertFunc never appears to fire - I've added console logging to write a message when the function is entered, and it never appears. I've run through the debugger in Chrome, with a breakpoint set in the callback, and it's never reached.
Obviously I'm doing something wrong either with the setTimeout call itself or how I'm trying to use it, but based on the documentation I've read so far I don't see what it could be. Is my concept of how to use it just plain wrong?
I'm an old C and C++ programmer who's only dabbled very lightly in HTML and scripting for the Web, so it's possible I just don't understand the right way to do things here.
EDIT
To address some of the comments, this is a working page - when I click OK the simulation runs and dynamically builds a table for output. It’s just that there are combinations of input parameters that cause it to run for an excessively long time (until the browser throws up a "this page is unresponsive" or I just close the tab).
I can trace the execution of the script in the debugger in Chrome, and it calls setTimeout and assigns a value to timerId, but the callback never actually executes. For the default parameters, the simulation runs to completion and builds a table on the page with the results.
Maybe this isn’t the right way to address the issue - maybe I need to add another button that calls a different function setting keepGoing to false.
EDIT2
I wrote up a quick and dirty prototype to just test the timeout functionality, and I think I know what my problem is. If I'm not in the middle of a loop, the callback executes as expected, but if I have a loop spinning it won't. So, I need to do some reading up on how to deal with asynchronous event handling.
If you're sure the while loop isn't preventing the next line from running, it's possible the script hasn't loaded yet and the function hasn't been defined by the time you assign the onclick attribute to your <input> element, try setting the defer tag to your script element to ensure the script is executed after the document has been parsed. Calling the function externally works fine otherwise.
In the following javascript code, I set a timer after 2 seconds, in which the handler is a function which alerts 'doneMessage'. When the prompt occurs, the next line should execute after I enter something or cancel the prompt.
function setTimer(doneMessage, n) {
setTimeout(function() {
alert(doneMessage);
}, n);
prompt('Enter a number');
doneMessage = "OUCH!";
}
setTimer("Cookies are done!", 2000);
So if I take more than 2 seconds on the 'prompt', the timer event should occur before the next line is executed. In this case 'doneMessage' should still be "Cookies are done!".
Why is "OUCH" alerted ?
Javascript is a single threaded language.
It cannot run more than 1 tasks at the same time.
Your current task is still being executed but held up by the prompt command.
Once you complete the prompt and exit the setTimer() function then that task is effectively finished.
At that point the doneMessage was set to "OUCH!"
Javascript is now free to do the next task.
Typically, it It loops all the timeout and see if there are any completed timer to execute and put them in a queue to execute next.
If you click on a button, it does not always immediately execute that button. It is place in the queue of task to execute next but given a high priority to make sure it is processed next. If Javascript wasn't currently in middle executing a task your button would be processed immediately.
Use the <input> tag and create your own prompt which is non-thread blocking and you will get the behaviour you want.
This is my demo code:
while (true) {
// Part one: Execute this code
input = prompt("Enter input"); // Now wait for user input
// Part two: Now execute this after getting user input
}
But, it is not working like I shown in the comments. It first asks the user for input, then loads part one and part two on the screen.
While executing JavaScript, browsers accumulate all changes to the DOM before doing a re-render. The prompt dialog delays completion of the JavaScript thread and delays the re-render operation.
To allow the browser to render accumulated changes, put the prompt and any subsequent operations in a setTimeout function.
document.write("I Want to Display this before Prompt");
$("div").addClass("blue")
setTimeout(function() {
input = prompt("test");
//PUT more code here
});
The setTimeout will hold its code block for the next render tick of the browser.
I have a sort of a strange problem with the structure of my JS code. It is a convertor from Brainfuck to JavaScript. Then it runs the code it has generated.
With this in mind, I have noticed a problem with the prompt(); command. It seems to, in some situations, show the prompt box before the previous command (changing the DOM) finishes.
My solution is to show an <input type="text"> box, initially set to display:none in CSS. However, I need a way to wait for the input to change. I tried this code:
while (document.getElementById("input") == ""){}
But this freezes the DOM. I need a way to passively wait for any user input, then continue with the script.
Keep in mind, I can't use onkeydown, external functions, etc.
Thanks!
There's no way to stop script execution besides using native functions like alert, confirm, prompt.
Try wrapping your prompt inside a setTimeout. Start with a value of 0 to see if it works.
setTimeout(function () {
prompt('your prompt');
}, 0);
Using setTimeout with a value of 0 pushes the execution of the prompt to the back of the event loop, and may possibly run after your DOM updates.
Read more about this technique here: https://www.quora.com/What-does-setTimeout-with-a-0ms-delay-do
To wait for user input in a non-blocking way, you need to run asynchronous code. This you do by providing a callback function which should be called when a certain event takes place. In your case, this could be when the input value is submitted:
var inp = document.getElementById("inp");
inp.addEventListener('change', function(e) {
var value = this.value;
alert('you entered: ' + value);
// all code that needs the value, should come here,
// or should be called from here.
});
Exit field to submit value:
<input id="inp">
Im trying to implement a simple blckjack game, the problem is that the timeOut function is not working as I expect. I wrote some debugging messages to help you understand what I mean. With two words what I see is that the function is called once and than for some reason it exits from the function, program continues executing itself and than retunrs to the timeOut function...
What I want is to pause the program execution to wait user to choose whether to request new card ot to stop.
Thank you in advance!
Where is the waitForUserInput() method being called? Also, why use a timeout for grabbing user input? Why not simply subscribe to the mouse click event?
setTimeout will not stop exection of a script. It is just for delayed execution. After you call it - execution of program will be continued as usual, but after specified time - function passed as a first parameter will be executed. To wait for user input - take a look at click/keyup/keydown etc. events.
You should not do things like below in JS. JS always single thread and such loops will just freeze your interface. In yor case it looks like you should place onclick event on card block and put there a code which will do what you need.
while(true){
waitForUserInput();
if(requestCard){
userHand.hitMe();
var userHandCards = userHand.printHand().split(",");
displayCard(userHandCards[cardIndex]);
cardIndex++;
//console.log(">"+userHand.score());
if(userHand.score()>21){
break;
}
}else{
break
}
};
What I want is to pause the program execution to wait user to choose whether to request new card ot to stop
You should place some buttons with onclick handlers specified. And just run code you need depending on clicked button. Right now I do not see how user can say to your program about his choice. If that is a keybord command ("s" pressed than stop, "n" - next card ) - you can try to use document.onkeyup.