Javascript does not print text immediately - javascript

I wrote this code which is supposed to say "hi" when I click the "hello" button:
<!DOCTYPE html>
<html>
<head>
<script>
var someLargeNumber = 5000000000;
function hello() {
document.getElementById('hi').innerHTML = "hi";
for(var i = 0; i < someLargeNumber; i++) {}
}
</script>
</head>
<body>
<p id="hi"></p>
<input type="button" value="hello" onclick="hello();">
</body>
</html>
It does say hi, but only after the for loop is finished. Why does this happen and how do I fix this?
Thanks

Why does this happen...
Because browsers run JavaScript on the main UI thread they use for updating the page, for a variety of reasons. So although you've shown the "hi" text, it doesn't get rendered until the JavaScript code running in response to the event completes.
...and how do I fix this?
Yield back to the browser after adding the text, before doing whatever it is that you're simulating with that loop. setTimeout with a delay of 0 is suitable for many cases:
var someLargeNumber = 5000000000;
function hello() {
document.getElementById('hi').innerHTML = "hi";
setTimeout(function() {
for(var i = 0; i < someLargeNumber; i++) {}
}, 0);
}
The JavaScript engine works basically in a loop with a task queue (the spec calls them "jobs"). It picks up a job from the queue, runs it to completion, and then looks for the next job. Browsers (usually) update the UI when the engine is between jobs. When an event occurs, a job is queued to call the event handler. The above just moves the loop into a new job it queues via setTimeout, so the browser has a chance after the event job and before the setTimeout job to update the UI.

As already answered browser has single UI thread.
Another option is to use Web Worker (provided you are not doing any DOM manipulations in the worker thread), which allows to run operations in an another thread.
Add another js file (say worker.js)
var someLargeNumber = 5000000000;
onmessage = function(e) {
console.log('Message received from main script');
for(var i = 0; i < someLargeNumber; i++) {}
console.log('Posting message back to main script');
postMessage('done');
}
Back in you main file
<head>
<script>
var myWorker = new Worker("worker.js");
function hello() {
document.getElementById('hi').innerHTML = "hi";
myWorker.postMessage('test');
console.log('Message posted to worker');
}
myWorker.onmessage = function(e) {
result.textContent = e.data;
console.log('Worker thread is complete');
}
</script>

Related

Is there a way to constantly update a variable?

I'm trying to make this thing where in one tab you type something, and in another it pops up. However, I have to constantly reload the page to get my next message. First, this is what I tried.
<!DOCTYPE html>
<html>
<body>
<p id="message"></p>
<script>
var x = localStorage.getItem('message')
var i;
for (i = 0; i < 5) {
document.getElementById('message').innerHTML = x;
}
</script>
</body>
</html>
HOWEVER, this puts the page in a constant reloading state. How would I do this? Thanks!
Use setInterval instead of a loop.
setInterval(function() {
var x = localStorage.getItem('message');
document.getElementById('message').innerHTML = x;
}, 1000);
This will update every second.
Instead of constantly polling for updates, you can set up an event listenener to catch every message that is sent. You can do this thanks to the storage event:
// Every time a change it made to this domain's localStorage (item added, changed, removed)
window.addEventListener('storage', function() {
// Update your DOM
document.getElementById('message').innerHTML = localStorage.getItem('message');
});
You have an infinite loop. A for loop has four steps.
Initialization: Declaring the variable and its initial value(Happens
once)
Condition: Checking the condition to continue the loop
Final
Expression: Usually where you handle your logic that will end your
loop like incrementing the i variable
Execution: Execute the code in the code block
Your method is working but it never stops working, hence why your browser doesn't stop loading.
The for loop moves too quickly anyway and it would be better to put to listen for a storage event update on your document.
var messageContainer = document.querySelector('#message')
window.addEventListener('storage', function() {
var text = localStorage.getItem('message')
messageContainer.textContent = text
}

Alert is not blocked by sync calls before that

I created this sample web page to create a delay by running the same blocking call multiple times. The issue here is, It's taking some time javascript to count to 50,000 (around 4 seconds) at least it looks like so in the javascript console but the alert call is running before chrome finish counting to 50,000. Why is that?
<html lang="en">
<head>
<title>Document</title>
</head>
<h1>Hello World</h1>
<body>
<script>
for(let i = 0; i < 50000; i++) {
console.log("Loaded")
}
alert("loaded")
console.log('WEB PAGE: Hello World')
</script>
</body>
</html>
Nothing is wrong what your seeing is the delay the browser creates.
Drop the logging in the for loop and use it just to loop that many times will make everything faster.
Note: your not really not describing the point. If the point is to wait for 50,000 what does that mean? is it seconds or less? Is this just a test for some slow api call? Or simply to write out 1-50,000? if you could explain more the answer may be much easier to provide.
see my comment for this add:
var count = 0;
for(let i = 0; i < 50000; i++) {
count++;
}
console.log(count);
console.log will queue the data and write it when the process ends, it doesn't write strings synchronously and because you queued a lot of data, the browser finished counting and showed the alert before the console data was shown.

Javascript function stops executing when another interaction occurs

In my code example below I have two buttons that do the following:
1) Clicking button 1 executes a while loop
2) Clicking button 2 executes a log statement
If you click button 1 and then click button 2, button 1 stops executing.
Is it possible to have button 1 continue to execute when another action occurs?
function buttonOneClick() {
let i = 0;
while (i < 1000) {
console.log('index is ', i);
i++;
}
}
function buttonTwoClick() {
console.log('button two clicked');
}
<button type="button" onclick="buttonOneClick()">Click Button One and Start Loop</button>
<button type="button" onclick="buttonTwoClick()">Click Button Two</button>
A solution for "JavaScript multi-threading": you could use webworkers for the problem. Put the code you want to execute in a webworker, that runs in the background (by definition):
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).
https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers
With this solution you can have your function continuously executed, and still have access to other functions.
Note that this is not a traditional webworker in my solution, but an inline webworker.
function buttonOneClick() {
// create an inline webworker
var blob = new Blob([
document.querySelector('#worker1').textContent
], {
type: "text/javascript"
})
// Note: window.webkitURL.createObjectURL() in Chrome 10+.
var worker = new Worker(window.URL.createObjectURL(blob));
worker.onmessage = function(e) {
console.log("Received: " + e.data);
}
worker.postMessage("hello"); // Start the worker.
}
function buttonTwoClick() {
console.log('button two clicked');
}
<button type="button" onclick="buttonOneClick()">Click Button One and Start Loop</button>
<button type="button" onclick="buttonTwoClick()">Click Button Two</button>
<script id="worker1" type="javascript/worker">
// This script won't be parsed by JS engines because its type is javascript/worker.
self.onmessage = function(e) {
for (let i = 0; i < 1000; i++) {
self.postMessage( 'message from worker ' + i)
}
};
// Rest of your worker code goes here.
</script>
To check the results, use the real console, and scroll back a some - you'll find your buttonTwoClick() console.log().
One note, though
Don't press Tidy in the snippet - it will mess up the worker, as it cannot "understand" what that is.

The JavaScript Event Loop and Web Workers

So I've been having a long talk with a colleague regarding the JavaScript event loop and the use of Web Workers. In a single Web page, different Web Workers have different stacks, heaps, and message queues, form here, specifically:
A web worker or a cross-origin iframe has its own stack, heap, and message
queue. Two distinct runtimes can only communicate through sending messages via
the postMessage method. This method adds a message to the other runtime if the
latter listens to message events.
but are all the messages executed inside the same event loop, or does each Web Worker have its own event loop?
I'm asking this because I have two Web Workers in a page, one executes a very computationally-heavy operation in sequence, while the other just handles a WebRTC connection.
I will not go into details but it seems to me that the computationally-heavy Web Worker is taking away so much computational time out of the JavaScript event loop that the other Worker, that only has to keep the connection alive (through heartbeat I suppose) isn't able to do so, and the connection is eventually lost.
This is what I believe. If that is not the case, and the two Web Workers work on different event loops then I cannot explain why the connection is lost when the load on the computing Web Worker is heavy (when the load is light then the connection is not lost).
Each worker has its own event loop. From the specification:
Each WorkerGlobalScope object has a distinct event loop, separate from those used by units of related similar-origin browsing contexts.
and then here:
The global scope is the "inside" of a worker.
...which is followed by the definition of the WorkerGlobalScope interface referenced in the earlier quote.
Your computation-heavy worker might be dominating the available processing time, but it isn't blocking the other worker's event loop.
We can also readily check this with a quick test:
page.html:
<!DOCTYPE HTML "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta charset="UTF-8">
<title>Two Workers</title>
<style>
body {
font-family: sans-serif;
}
pre {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div>Fast: <span id="fast"></span></div>
<div>Slow: <span id="slow"></span></div>
<script>
(function() {
var fastWorker = new Worker("fastworker.js");
var fast = document.getElementById("fast");
var slowWorker = new Worker("slowworker.js");
var slow = document.getElementById("slow");
fastWorker.addEventListener("message", function(e) {
fast.innerHTML = e.data || "??";
fastWorker.postMessage("ping");
});
slowWorker.addEventListener("message", function(e) {
slow.innerHTML = e.data || "??";
slowWorker.postMessage("ping");
});
fastWorker.postMessage("start");
slowWorker.postMessage("start");
})();
</script>
</body>
</html>
slowworker.js:
var counter = 0;
self.addEventListener("message", function(e) {
var done = Date.now() + 1000; // 1 second
while (Date.now() < done) {
// Busy wait (boo!)
}
++counter;
self.postMessage(counter);
});
fastworker.js:
var counter = 0;
self.addEventListener("message", function(e) {
var done = Date.now() + 100; // 100ms
while (Date.now() < done) {
// Busy wait (boo!)
}
++counter;
self.postMessage(counter);
});
As you can see, "fast"'s number goes up much more quickly than "slow", showing it's processing its messages.
(I could have made one worker file and sent the delay in the start command, but...)

WebSocket sends message only after the loop get completed

I have a code like below:
var ws = new WebSocket("ws://localhost");
ws.onopen = function() {
// Long running loop
for (var i = 0; i < 1000000; i++) {
ws.send(i);
console.log(i);
}
};
The server only receives message (or I believe the client only starts sending message) after the loop gets completed. Why is this so?
Some areas of execution with a page are:
JavaScript
DOM rendering
Network
I've not tested this for a while, but I'm assuming it is still the case, that if you execute a function (or run code in one scope) then if you make a call to update the DOM or make a network request that won't happen until the current scope exists.
e.g.
function doSomething() {
document.body.innerHTML += 'hello';
var ws = new WebSocket('ws://my-websocket-endpoint.com');
ws.onopen = function() { alert( 'awesome!' ); };
}
doSomething();
In the code above the following will happen:
doSomething executes and the internal code is run.
doSomething returns and the scope changes
'hello' then appears in the DOM as the browser gives the UI thread a chance to run any pending updates
The WebSocket connection is made
It may be a while until the alert fires.
Looking specifically at the WebSocket example. If you think about the fact we can add event handlers after we create the WebSocket instance this means that the connection doesn't occur as soon as we call the constructor. The connection is only attempted when the current scope of executions completes. I can't 100% confirm this, but it's also highly unlikely that the WebSocket.send function actually sends any data until the scope of execution completes e.g.
var ws = new WebSocket('ws://my-websocket-endpoint.com');
function sendStuff() {
for( var i = 0; i < 10000; ++i ) {
ws.send( i );
}
}
// Assume we are connected
sendStuff();
In the above code I would expect:
sendStuff to be called, run the loop and exit
The browser to then deal with anything else that's pending, including network activity. This includes actually sending the data
It doesn't answer your question but its worth noting that your code looks incorrect.
Your new WebSocket call initiates an asynchronous handshake with the server. When this completes, any onopen callback you registered on your websocket will run. It is only at this time you can call ws.send and expect the server to receive your message.
You could then rework your example to
var ws = new WebSocket("ws://localhost");
ws.onopen = function() {
for (var i = 0; i < 1000000; i++) {
ws.send(i);
console.log(i);
}
};

Categories

Resources