Javascript function stops executing when another interaction occurs - javascript

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.

Related

Making Modal Dialog work with Asynchronous Google Script

I need to run through an array of roles and open a modal dialog (in HTML) for each. I had a problem where each next dialog gets opened before I close the previous dialog (because of asynchronous Google Script.
I have tried implementing a solution by setting a while loop for Utilities.sleep() and adding a global variable 'sleeping' that becomes false when the modal dialog is closed.
However, now only the first dialog opens and the code does not run through the full 'for' loop.
function nightStart(nightNumber, playersArray, roleList) {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var range = sheet.getRange("Controls!G3:G1000");
var wakeupOrder = [];
var sleeping;
var role;
//collecting the array to define in what order roles wake up
for (var i = 1; i<=20; i++) {
var cellValue = range.getCell(i,1).getValue();
wakeupOrder.push(cellValue);
}
//the FOR loop that I am trying to make work (open Dialog for each role)
for (index in wakeupOrder) {
role = wakeupOrder[index];
if (roleList.indexOf(role) != -1) {
sleeping = true;
roleWakeUp(role, playersArray, roleList);
do {
Utilities.sleep(2000);
//calling global sleeping parameter that is defined as FALSE in the 'nightTargetSelection' function
sleeping = PropertiesService.getScriptProperties().getProperty('sleeping');
} while (sleeping != false);
}
}
}
//below is the function that opens the modal dialog (but the server side code still keeps running).
function roleWakeUp (role, playersArray, roleList){
//I have removed all code from here for Stack Overflow. The only part that I believe is important is that it opens an HTML dialog with a form
SpreadsheetApp.getUi().showModalDialog(actionInputDlg, wakeUpText);
}
//Below function is called by the client on HTML form submission. After this form is submitted I need the next dialog to open (i.e need the Utilities.sleep to stop running
function nightTargetSelection (selected, playerNumber){
var sleeping = false;
PropertiesService.getScriptProperties().setProperty('sleeping', sleeping);
}
I need an HTML dialog to open for each 'role' in the 'wakeupOrder' array (if the role exists in 'roleList'). Each next dialog needs to open only after the submission of the previous dialog.
You want to open several dialogs in order.
When the process is finished on a dialog, you want to open next dialog.
Namely, you don't want to open the next dialog before the current job is finished.
If my understanding is correct, how about this answer?
When SpreadsheetApp.getUi().showModalDialog() is opened in order, the dialog is overwritten. I think that this is the reason of your issue. So, here, I would like to introduce a sample script. In this sample script, the next dialog is opened from the current dialog. The flow of this sample script is as follows. Please think of this as just one of several answers.
Open a dialog by running start().
When "ok" button is clicked, the next dialog is opened by including the next job.
By this, each job can be completely done.
When all jobs were finished, done() is run and the dialog is closed.
Sample script:
When you use this script, please copy and paste "Code.gs" and "index.html" to "script" and "HTML" on your script editor, respectively. And please run start(). This sample script supposes that you are using the container-bound script of Spreadsheet.
Code.gs: Google Apps Script
// When all jobs were finished, this function is called.
function done(e) {
Logger.log(e)
}
// Open a dialog
function openDialog(jobs, i) {
var template = HtmlService.createTemplateFromFile('index');
template.jobs = JSON.stringify(jobs);
template.index = i;
SpreadsheetApp.getUi().showModalDialog(template.evaluate(), "sample");
}
// Please run this script
function start() {
var jobs = ["sample1", "sample2", "sample3"];
openDialog(jobs, 0);
}
index.html: HTML and Javascript
<div id="currentjob"></div>
<input type="button" value="ok" onclick="sample()">
<script>
var jobs = JSON.parse(<?= jobs ?>);
var index = Number(<?= index ?>);
document.getElementById("currentjob").innerHTML= "currentJob: " + jobs[index] + ", index: " + index;
function sample() {
if (index < jobs.length - 1) {
google.script.run.openDialog(jobs, index + 1); // Modified
} else {
google.script.run.withSuccessHandler(()=>google.script.host.close()).done("Done.");
}
}
</script>
References:
Class HtmlTemplate
withSuccessHandler()
Note:
This is a simple sample script. So please modify this for your situation.
If I misunderstood your question and this was not the result you want, I apologize.

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...)

Javascript does not print text immediately

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>

Prevent alert() from halting the execution of JavaScript

First of all , i know that JavaScript is single threaded.
Lets say i have a counter that outputs its values every second.
var counter = 0;
setInterval(function(){
console.log("Interval: " + ++counter);
}, 1000);
At some point, i want to trigger some code that halts the execution of the script.
For example, an alert message.
setTimeout(function(){
alert("Some alert message");
},10000);
At this point when the alert message popped, the interval code will halt.
Is there a trick so it will not stop executing?
Depending on the browsers you need to support (check browser compatibility), you could use WebWorkers to execute scripts in parallel threads. You just need to split up your code into separate files, so that the code, which is to run in a worker thread, is in its own file. Adjusting your example without going into details of the worker you could set up a file worker.js containing the following code:
// file worker.js
var counter = 0;
setInterval(function(){
console.log("Interval: " + ++counter);
// The worker will live until you kill it.
if (counter > 100) self.close();
}, 1000);
Your main JavaScript file will then set up the new worker to have it run in a separate worker thread.
// Set up the new worker in a separate thread.
var worker = new Worker("worker.js");
setTimeout(function(){
// This will not stop the worker from logging to console.
alert("Some alert message");
},10000);
Thus, the alert() in the main thread will not stop the execution of the worker thread, which will continue logging to console.
See this Plunk for a live demo.
First of all: Are you aware that if the code doesn't halt, the popups will stack on screen to infinity?
Alert is primarily used for simple web pages and debug purposes (it's the simplest breakpoint there is). If you want to notify the user on our site, use some framework or just make HTML popup. I just recently needed this and downloaded Jquery UI framework. I'm not the kind of guy who uses jQuery everywhere for everything, but getting HTML popups align nicely in browser is boring and tedious.
In JQuery UI you just do:
HTML:
<div id="my-dialog">Interval: <span id="counter">0</span></div>
JavaScript:
// Configure the div as dialog, call this only once
$("#my-dialog").dialog({modal:true,
autoOpen: false,
buttons: {
Ok: function() {
$( this ).dialog( "close" );
}
}
});
var counter = 0;
var intervalID = setInterval(function() {
// Update counter info
$("#count").html(++counter);
// Show dialog
$("#my-dialog").dialog("open");
}, 1000);
Demo

How to capture time consumed in a user action in Javascript?

In our RIA application, created using ExtJs, we need to capture the time consumed by a user action.
For example, user clicked at a button 'Open' which opens a window, then the time consumed in opening of the window needs to be captured.
We have tried using Selenium IDE, but it only records the actions and executes them back and doesn't log the time consumed in the commands.
We have also tried using 'Profiler' feature of browsers (start profiling before action and stop profiling after action is over), but then that is manual.
Thus, could someone guide at - how to capture the time taken by a user action in Javascript in an automated manner?
Thanks for any help in advance.
This is a rough sketch of a global event tracking function:
(function() {
var pairs = {};
eventLogger = function(e) {
var ts = new Date().getTime();
if (pairs[e]) {
var taken = ts - pairs[e],
img = new Image();
img.onload = function() { };
img.src = '/path/to/logger?evt=' + encodeURIComponent(e) + '&t=' + encodeURIComponent(taken);
delete pairs[e];
} else {
pairs[e] = ts;
}
}
}());
It matches pairs of event names; first one starts the timer, second one makes a server request to log the time taken for that event.
Then, you would log events like these:
eventLogger('loadContacts');
// start code that loads contacts
// ...
// later
eventLogger('loadContacts'); // done loading
It doesn't distinguish between start and stop events, so if you may have overlapping start and stop times, you may need to make some tweaks.

Categories

Resources