Chrome, FF. Opera and probably others browser show only the 100000 number at end of process, but i want see displayed in sequence 1..2..3..4...100000.
This code doesn't work well:
<html>
<head>
</head>
<body>
<button type="button" onclick="showSequence();">Show the numbers one at a time!</button>
<p id="herethenumbers">00</p>
<script>
function showSequence() {
el = document.getElementById('herethenumbers');
for(var nn=0;nn<=100000;nn++){
el.innerHTML = nn;
}
}
</script>
</body>
</html>
window.setTimeout isn't possible using when you don't know the execution time of a given process and even changing the attributes of the main div object (visibility e.g.) does not work for me.
Thanks at all.
UPDATE
Here a partial solution.
Allows you to view the status of a long process, for now, unfortunately only the beginning and the end of the (single) process.
<html>
<head>
</head>
<body>
<button type="button" onclick="executeMultiLongProcess();">Launch and monitoring long processes!</button>
<p id="statusOfProcess"></p>
<script>
var el = document.getElementById('statusOfProcess');
function executeMultiLongProcess() {
processPart1();
}
function processPart1() {
el.innerHTML = "Start Part 1";
setTimeout(function(){
for(var nn=0;nn<=100000000;nn++){ //..
}
el.innerHTML = "End Part 1";
window.setTimeout(processPart2, 0);
},10);
}
function processPart2() {
el.innerHTML = "Start Part 2";
setTimeout(function(){
for(var nn=0;nn<=100000000;nn++){ //..
}
el.innerHTML = "End Part 2";
window.setTimeout(processPartN, 0);
},10);
}
function processPartN() {
el.innerHTML = "Start Part N";
setTimeout(function(){
for(var nn=0;nn<=100000000;nn++){ //..
}
el.innerHTML = "End Part N";
},10);
}
</script>
</body>
</html>
I want to suggest using window.requestAnimationFrame rather than setTimeout or setInterval as it allows you to wait for the browser to render changes and right after that, execute some code. So basically you can do:
window.requestAnimationFrame( () => {
el.innerHTML = nn;
} );
I changed your function to be recursive. This way I can call the function to render the next number inside the window.requestAnimationFrame callback. This is necessary, as we ought to wait for the browser to render the current number and just after that, instruct the browser to render the next one. Using window.requestAnimationFrame inside the for loop would not work.
el = document.getElementById( 'herethenumbers' );
function showSequence( nn=0 ) {
if( nn <= 100000 ) {
window.requestAnimationFrame( () => {
el.innerHTML = nn;
showSequence( nn + 1 );
} );
}
}
<button type="button" onclick="showSequence();">
Show the numbers one at a time!
</button>
<p id="herethenumbers">00</p>
Use window.setInterval:
function showSequence() {
var el = document.getElementById('herethenumbers');
var nn = 0;
var timerId = setInterval(countTo100, 80);
function countTo100(){
el.innerHTML = nn;
nn++;
if (nn>100) clearTimeout(timerId);
}
}
<button type="button" onclick="showSequence();">
Show the numbers one at a time!
</button>
<p id="herethenumbers">00</p>
Update
the scenario is a bit different. You have a javascriot process that starts and ends without interruptions, it can work several minutes in the meantime, inside it, must show on screen the status of its progress.
JavaScript is single-threaded in all modern browser implementations1. Virtually all existing (at least all non-trivial) javascript code would break if a browser's javascript engine were to run it asynchronously.
Consider using Web Workers, an explicit, standardized API for multi-threading javascript code.
Web Workers is a simple means for web content to run scripts in background threads. The worker thread can perform tasks without interfering with the user interface. In addition, they can perform I/O using XMLHttpRequest (although the responseXML and channel attributes are always null). Once created, a worker can send messages to the JavaScript code that created it by posting messages to an event handler specified by that code (and vice versa).
For more information, see MDN Web API Reference - Web Workers.
Sample code below using setTimeout, only counts up to 100 for the sample.
Might want to check out Difference between setTimeout with and without quotes and parentheses
And then there is also: 'setInterval' vs 'setTimeout'
var delayId = null, frameTime = 25, countTo = 100, el, nn = 0;
function increment(e) {
if (delayId) {
window.clearTimeout(delayId);
}
el.textContent = nn++;
if (nn <= countTo) {
delayId = window.setTimeout(increment,frameTime);
}
}
window.onload = function() {
el = document.getElementById('herethenumbers');
var b = document.getElementById('start');
b.addEventListener("click",increment,false);
}
<button type="button" id="start">Show the numbers one at a time!</button>
<p id="herethenumbers">00</p>
Using requestAnimationFrame as suggested by Jan-Luca Klees is the solution to my problem, here a simple example of use of requestAnimationFrame. Allows you to run one or more long-duration processes with the interaction with objects on screen (a popup for example or others), requestAnimationFrame tells the browser you want to run an animation and you want the browser to call a specific function to update a animation.
<html>
<head>
</head>
<body>
<button type="button" onclick="startProcesses();">Start long duration process!</button>
<p id="status">Waiting to start processes</p>
<script>
el = document.getElementById('status');
var current_process = 1;
var total_process = 2;
var startEnd = 'S';
function startProcesses() {
function step(timestamp) {
if(current_process==1 && startEnd=='E') res = process1();
if(current_process==2 && startEnd=='E') res = process2();
//..n processes
if(startEnd=='S') el.innerHTML = "Process #"+current_process+" started..";
if(startEnd=='E') el.innerHTML = "Process #"+current_process+" "+res;
if(startEnd=='S' || current_process<total_process) {
if(startEnd=='E') current_process++;
startEnd = (startEnd=='S'?'E':'S');
window.requestAnimationFrame(step);
}else{
el.innerHTML = "Process #"+current_process+" "+res;
}
}
window.requestAnimationFrame(step);
}
function process1() {
for(var nn=0;nn<=10000;nn++){
console.log(nn);
}
return "Success!"; //or Fail! if something went wrong
}
function process2() {
for(var nn=0;nn<=10000;nn++){
console.log(nn);
}
return "Success!"; //or Fail! if something went wrong
}
</script>
</body>
</html>
Related
I have a web page that does a lot of calculations as part of it's loading.
I want to display a spinner bar while the page is "thinking" so that the end user knows the delay is to be expected.
The way I was going to do this is to display a GIF file that would be a spinning wheel, and hide the table that would be my output.
The problem is, once the function starts, updates to the page seem to freeze until the function is done. Therefore, the end user never sees the "In Process" section.
The sample code I put together to demonstrate my problem is:
<!DOCTYPE html>
<html>
<body>
<script>
function show(id, value) {
document.getElementById(id).style.display = value ? 'block' : 'none';
}
function Processing(x)
{
if (x === true)
{
show('page', false);
show('loading', true);
}
else
{
show('page', true);
show('loading', false);
}
}
function MainProcess()
{
Processing(true) // Set to "Show processing..."
var start = new Date().getTime(); // Sleep a few seconds
for (var i = 0; i < 5; i++) {
if ((new Date().getTime() - start) < 3000) { i = 1 }
}
Processing(false) // Set to "Show Completed processing..."
}
window.onload = function() {
show('page', false);
show('loading', false);
};
</script>
<div id="loading">
<h1>Processing your request.</h1>
</div>
<div id="page">
<DIV class="body">
<h3>Done Processing your request.</h3>
</DIV>
</div>
<div id="buttons">
<button onclick="MainProcess();">Start Timer</button>
<button onclick="Processing(false);">Set to Completed</button>
<button onclick="Processing(true);">Set to Processing</button>
</body>
</html>
When I run this, it presents three buttons.
When you hit "Start Timer", it should show processing for a few seconds, then display done. Instead, the button changes colors and otherwise appears to do nothing till the timer is done, and it displays complete.
(I am new to JavaScript but a seasoned pro in several other languages. Is there a "DoEvents" type of call I can make like in Visual Basic or Powershell?)
The problem seems to be that the DOM update (i.e. changing the display properties on your DIVs) doesn't complete before your processing function starts. One option would be to use window.setTimeout to delay the start of your processing function to allow the DOM update to complete:
function MainProcess()
{
Processing(true); // Set to "Show processing..."
// Pause for 100 ms before starting time-consuming code to allow dom update to c
var domUpdateDelay = 100;
window.setTimeout(function() {
var start = new Date().getTime(); // Sleep a few seconds
for (var i = 0; i < 5; i++) {
if ((new Date().getTime() - start) < 3000) { i = 1 }
}
Processing(false) // Set to "Show Completed processing..."
}, 100);
}
window.setTimeout takes two arguments: the first argument is a function that runs after x milliseconds, where x is the second argument.
The problem with this approach is that an appropriate delay for setTimeout will vary across machines/users/browsers/runs. However, my guess is that this will probably not be an issue in 99% of cases.
I think you can take advantage of a function like setTimeout (https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout), say:
setTimeout(function() {
Processing(false);
}, 5000);
I think what's happening is you're nearly crashing the browser with that loop. I may have misunderstood what parts of your question were just there for demonstration purposes.
If I write the html:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<h1 id="message">
</h1>
and the JS:
messages = ["Here", "are", "some", "messages."]
$(function() {
for (var i = 0; i < messages.length; i++) {
$('#message').html(messages[i]).delay(1000);
}
});
and load the page, I expect to see each string in the array show up with a delay in between. However, all I see is "messages." appear. It seems that the for loop iterates immediately through each value in the array before performing any delay.
I have seen another method for getting the desired visual result (How can I change text after time using jQuery?), but I would like to know why the earlier method does not work. What is going on when this code is executed?
This is how i would delay my message changing.
function delayLoop(delay, messages) {
var time = 100;
$(messages).each(function(k, $this) {
setTimeout(function()
{
$("#message").html($this);
}, time)
time += delay;
});
}
delayLoop(1000, ["Here", "are", "some", "messages."]);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="message">
</div>
All I did was for each message delay by an additional delay time.
It works in async mode so its not ui blocking and the messages will display a second after one another.
EDIT:
Removed the .delay from the .html it is redundant.
Note that jQuery's delay is specifically for effects; methods like html do not use the effects queue and are therefore not affected by delay.
This is a problem better solved with JavaScript's native setTimeout function. There are many ways to do this; in fact, you don't even need jQuery!
let messages = ["Here", "are", "some", "messages."];
let delay = 1000;
let header = document.getElementById("message");
messages.forEach(function(message, i) {
setTimeout(function() {
header.innerText = message;
}, delay * i);
});
<h1 id="message" />
You would need something along the lines of
$(function() {
for (var i = 0; i < messages.length) {
var done=false;
$('#message').html(messages[i]).delay(1000).queue(function(){
done=true;
$(this).dequeue();
});
if(done==true){
i++;
}
}
});
Thank you for the answers and comments--very helpful.
I also found this post helpful: Node.js synchronous loop, and from it wrote this (which also works):
function changeText() {
var msg = messages.shift();
$('#message').html(msg).show(0).delay(1000).hide(0, function() {
if (messages.length > 0) {
changeText();
}
});
}
(I used .show and .hide because without them only one of the array values appeared. I'm not sure why that is, but that's a question for another time.)
I am getting an issue with a large amount of processing causing the non-responsive script error in IE8 (and no, I cannot make the users use a better browser).
I then read that it should be possible to split up the tasks and cede control back to the browser in between different parts of the validation. So I decided to make a simple example based on some code I found to figure out where the breaking points are. The real code is doing lots of jquery validationengine processing.
I tried to use jsFiddle but I can't get jsFiddle to run in IE8. Bummer. So, I'll have to share inline here.
When I first load it, it seems to work just fine. I push the button and both functions finish without a problem. However, subsequent pushes causes an unresponsive script error. I've played around with the number of loops in my simulated work function. Much more than 1.25 million loops and it dies with unresponsive script.
Shouldn't separate calls to the onClick start the non-responsive counter anew? What am I missing here?
<html>
<head>
<script>
var progress = null;
var goButton = null;
window.onload = function() {
progress = document.getElementById("progress");
goButton = document.getElementById("goButton");
}
function runLongScript(){
// clear status
progress.value = "";
goButton.disabled=true;
var tasks = [function1, function2];
multistep(tasks,null,function() {goButton.disabled=false;});
}
function function1() {
var result = 0;
var i = 1250000;
for (;i>0; i--) {
result = result + 1;
}
progress.value = progress.value + "f1 end ";
}
function function2() {
var result = 0;
var i = 1250000;
for (;i>0; i--) {
result = result + 1;
}
progress.value = progress.value + "f2 end";
}
function multistep(tasks, args, callback){
var tasksClone = tasks.slice(0); //clone the array
setTimeout(function(){
//execute the next task
var task = tasksClone.shift();
task.apply(null, args || []);
//determine if there's more
if (tasksClone.length > 0){
setTimeout(function () {
multistep(tasksClone, args, callback);
}, 100);
} else {
callback();
}
}, 100);
}
</script>
</head>
<body>
<p><input type="button" id="goButton" onClick="runLongScript();" value="Run Long Script" /></p>
<input type="text" id="progress" />
</body>
</html>
You're never calling clearTimeout() to remove the one currently running when the button has been pressed already. Add an if statement before you start another setTimeout and check to see if one is already running, clear it if it is, and then continue. Here's a link that should help you if you have any questions: https://developer.mozilla.org/en-US/docs/Web/API/window.clearTimeout
I've a jsp page which sets 'timestamp' attribute to certain HTML elements. I use the value of these 'timestamp' to display time elapsed in the format - "updated 10 seconds ago" (as tooltips)
I've created a static HTML page for the demonstration of my issue.
This is my code:
<html>
<head>
<script type = "text/javascript">
function setTime() {
var currentDate = new Date();
var elem = document.getElementsByClassName('supermaxvision_timestamp');
if(elem) {
for (var i = 0; i < elem.length; i++) {
var timestamp = elem[i].getAttribute('timestamp');
if(timestamp) {
var startTimestamp = new Date();
startTimestamp.setTime(timestamp)
var difference = currentDate.getTime() -startTimestamp.getTime();
elem[i].innerHTML = difference + " milliseconds";
}
}
}
setInterval(setTime, 1000);
}
</script>
</head>
<body>
<div class='supermaxvision_timestamp' timestamp='1353389123456' ></div>
<div class='supermaxvision_timestamp' timestamp='1353389234567' ></div>
<div class='supermaxvision_timestamp' timestamp='1353389345678' ></div>
<div class='supermaxvision_timestamp' timestamp='1353389456789' ></div>
<div class='supermaxvision_timestamp' timestamp='1353389567890' ></div>
<button onclick="setTime()">start</button>
</body>
</html>
you can just copy paste this code into a text file and open it in a browser (click 'start' button only once).
The problem is that initially the values of my div will update once every second ( as the code - setInterval(setTime, 1000)). But slowly the update interval decreases and values gets updated instantaneously. And within a minute the browser stops responding.
I'm not calling setInterval from within the loop. What is possibly wrong here?
Also, this code doesn't work in IE.
setInterval(fn, ms) says run fn every ms milliseconds, from now until I clear this interval. But on each call, you set a new interval, identical to the last.
So simply change setInterval to setTimeout which does not repeat, and only calls the function provided once. setTimeout can emulate setInterval by calling a function that sets a new timeout recursively. If you do that with intervals, you schedule more and more intervals that never stop. And each time it calls itself, the number of scheduled intervals double. It gets out of hand quickly...
Alternatively, you can move the setInterval out of the setTime function and only call it once, which will keep it being called every second. Like say:
// button calls this.
function startTime() {
setInterval(setTime);
}
function setTime() {
// all that code, but minus the setInterval at the end
}
You're calling setInterval recursively. Every time a new interval is created, that interval creates a new interval. Eventually the browser cannot handle it.
Maybe you would rather something like this?
<button onclick="setInterval(setTime, 1000)">start</button>
setInterval begins a repeating function - as it is right now setTime does it's loop and logic then calls setTimeout every second, each setTimeout call then starts another repeated call to itself every second. if you use setTimeout instead, it will be called once only, but my suggestion would be that instead you simply run setInterval outside your function declaration, like:
<html>
<head>
<script type = "text/javascript">
function GEBCN(cn){
if(document.getElementsByClassName) // Returns NodeList here
return document.getElementsByClassName(cn);
cn = cn.replace(/ *$/, '');
if(document.querySelectorAll) // Returns NodeList here
return document.querySelectorAll((' ' + cn).replace(/ +/g, '.'));
cn = cn.replace(/^ */, '');
var classes = cn.split(/ +/), clength = classes.length;
var els = document.getElementsByTagName('*'), elength = els.length;
var results = [];
var i, j, match;
for(i = 0; i < elength; i++){
match = true;
for(j = clength; j--;)
if(!RegExp(' ' + classes[j] + ' ').test(' ' + els[i].className + ' '))
match = false;
if(match)
results.push(els[i]);
}
// Returns Array here
return results;
}
function setTime() {
var currentDate = new Date();
var elem = GEBCN('supermaxvision_timestamp');
if(elem) {
for (var i = 0; i < elem.length; i++) {
var timestamp = elem[i].getAttribute('timestamp');
if(timestamp) {
var startTimestamp = new Date();
startTimestamp.setTime(timestamp)
var difference = currentDate.getTime() -startTimestamp.getTime();
elem[i].innerHTML = difference + " milliseconds";
}
}
}
}
</script>
</head>
<body>
<div class='supermaxvision_timestamp' timestamp='1353389123456' ></div>
<div class='supermaxvision_timestamp' timestamp='1353389234567' ></div>
<div class='supermaxvision_timestamp' timestamp='1353389345678' ></div>
<div class='supermaxvision_timestamp' timestamp='1353389456789' ></div>
<div class='supermaxvision_timestamp' timestamp='1353389567890' ></div>
<button onclick="setInterval(setTime, 1000)">start</button>
</body>
</html>
Also, the reason this is not working in IE is that it does not properly support the getElementsByClassName method of document. I found that out here: IE 8: Object doesn't support property or method 'getElementsByClassName' and Rob W also gives a good explanation there, but for a quick answer I have modified my code above to work in IE, using querySelectorAll
Derp, thats a jQuery method Chris, why would you just assume people use jQuery. getElementsByClassName & IE8: Object doesn't support this property or method includes an answer from ascii-lime which implements it's own version of getElementsByClassName. There no benefit to me copying all the code to here, but go have a look if you don't want to use jQuery.
OK, I just said there was no point, but I've copied all the code here anyway, above is a working, tested (on ie and ff) example of what you want
I am aware that when coding an extension, there is no way we can delay a function call except for using a setTimeout call but here's what I am trying to achieve in a plugin that I am developing for Firefox (this is not for Javascript embedded into a web page by the way):
for (var i = 0; i < t.length ; i++) {
//Load a URL from an array
//On document complete, get some data
}
The idea is simple. I have an array of URLs that I want to parse and extract some data out of. Each of these URLs take some time to load. So, if I try to get some data from the current page without waiting for the page to load, I will get an error. Now, the only way to do this as I know is as follows:
firstfunction: function() {
//Load the first url
setTimeout("secondfunction", 5000);
}
secondfunction: function() {
//Load the second url
setTimeout("thirdfunction", 5000);
}
And so on... I know this is obviously wrong.. I was just wondering how people achieve this in Javascript...
EDIT: Sorry about not being more detailed...
I'm not convinced that this type of foolery is necessary but I'm not an extension dev so who knows. If this is the approach you want, then just have the setTimeout call refer to the same function:
var index;
firstfunction: function() {
// do something with `index` and increment it when you're done
// check again in a few seconds (`index` is persisted between calls to this)
setTimeout("firstfunction", 5000);
}
I am not sure how to do this from a plugin, but what I've done with iframes in the past is attach a callback to the target document's onLoad event.
Maybe something like:
var index = 0;
var urls = [ ..... ];
function ProcessDocument() { ....; LoadNextDocument(); }
function LoadNextDocument() { index++; /* Load urls[index] */; }
document.body.onLoad = ProcessDocument;
Somewhere in there you'd need to test for index > urls.length too for your end condition.
I had same problem but I used recursion instead of looping.
Below is the running code which changes the innerHTML of an element by looping through the list. Hope its helpful.
<Script type="text/javascript">
var l;
var a;
function call2()
{
l = document.getElementById('listhere').innerHTML;
a = l.split(",");
call1(0);
}
function call1(counter)
{
if(a.length > counter)
{
document.getElementById('here').innerHTML = a[counter];
counter++;
setTimeout("call1("+counter+")",2000);
}
}
</Script>
<body onload="call2()">
<span id="listhere">3,5,2,8</span><Br />
<span id="here">here</span>