The following simple example is supposed to print a number in the console after clicking the button. If you click more than once in a short period of time, it will wait until all tasks are finished to print all the results (after calling awaitAll).
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/queue-async/1.0.7/queue.min.js"></script>
</head>
<body>
<button id="send">Send</button>
</body>
<script>
var button = document.querySelector('#send');
var q = queue(1);
var i = 0;
button.addEventListener('click', function() {
var message = i++;
q.defer(function(message, callback) {
setTimeout(function() {
callback(null, message);
}, 3000);
}, message);
});
q.awaitAll(function(err, result) {
console.log(result);
});
</script>
</html>
This works as intended. However, what happens if I want to reuse the queue again? For example, click on the button once and it will print [0]. Then click again and it will print [0,1]. The queue will keep track of previous tasks even after q.awaitAll has been called. I guess that could be useful but I have some concerns when the user provides tasks to the queue and after calling the callback, q.awaitAll outputs an array with all the previous results. The user probably needs the last one and returning everything seems a bit wasteful.
Is there some way to reset the queue?
By the way, I don't want to do this:
button.addEventListener('click', function() {
var q = queue(1);
var message = i++;
q.defer(function(message, callback) {
setTimeout(function() {
callback(null, message);
}, 3000);
}, message);
});
Here the queue doesn't work. In this case, I would be able to click several times and generate many queues running simultaneously.
Thanks
Related
I am creating a script to automate a data entry process on a server monitoring website. The site does not have an API for this specific data entry type, so I am using JavaScript to automate the mouse clicking and data entry process.
The script returns the correct document.getElementById("") value for getElement statement. However, it only returns the correct value whenever I manually execute line of individually. If I run the entire script, then the code breaks on one single line.
`var namesAndAddresses = { 'DomainName': 'IP' };
//Function to press sidebar "add device button"
function MenuFunction() {
MenuId = document.getElementById("menurow_Add/Import Devices").firstChild.nextElementSibling;
MenuId.click()
}
//Function to press "add device manually button"
function AddDeviceFunction() {
AddDeviceButton = document.getElementById("addDeviceButtonId_label");
AddDeviceButton.click();
}
//Function to add device information and then press the save button
function AddDeviceInformationFunction(domain, address) {
FQDN = document.getElementById("deviceNameId").value =
domain; //FQDN;
deviceClass = document.getElementById("deviceClassId").value =
"Switch/Router";
OS = document.getElementById("deviceOsId").value =
"Other Operating System";
ipAddress = document.getElementById("customUriId").value =
address; //DictionaryID;
licenseMode = document.getElementById("licenseModeId").value =
"Professional Mode";
saveButton = document.getElementById("cancelButtonId"); //change to save button
saveButton.click();
}
//manually wait function
function pause(milliseconds) {
var dt = new Date();
while ((new Date()) - dt <= milliseconds) { /* Do nothing */ }
}
//For loop to add each domain and ip that is listed in the dictionary
for (var [domainName, IP] of Object.entries(namesAndAddresses)) {
window.self = document;
//Function to press sidebar "add device button"
MenuFunction();
//Insert wait for sub-page to load here
pause(3000);
//Function to press "add device manually button" **THIS IS THE FUNCTION THAT RETURNS NULL**
AddDeviceFunction();
//Insert wait for objects to load here
pause(5000);
//Function to add device information and then press the save button
AddDeviceInformationFunction(domainName, IP);
//Insert wait for objects to load here
pause(5000);
};`
HTML code is below:
enter image description here
I have tried adding in a manual "wait" because window.onload does not work due to all the HTML being loaded already. I also tried adding a try/catch statement within a loop, so that it could make the attempt 10 times over a period of 10 seconds. That didn't work either.
Your code's pause function is blocking.
What this means is that since it "waits" using a while loop, it is not actually waiting but doing a lot of useless work, using up 100% of the CPU, and preventing anything else from actually loading.
Because of this, I can only presume the elements that are supposed to appear when loading do not get a chance to, meaning getElementById returns null (they do not exist yet)
Consider using something like setTimeout, which will properly wait, without preventing other work from being done.
Replace your for loop near the end with
var entries = Object.entries(namesAndAddresses)
window.self = document;
function processEntry(){
if(!entries.length){
// Done
return
}
// Get (and remove) the first element in the array
var [domainName, IP] = entries.shift()
//Function to press sidebar "add device button"
MenuFunction();
//Wait for sub-page to load using setTimeout, which will run this code after 3000ms
setTimeout(function(){
//Function to press "add device manually button" **THIS IS THE FUNCTION THAT RETURNS NULL**
AddDeviceFunction();
//Wait for objects to load here
setTimeout(function(){
//Function to add device information and then press the save button
AddDeviceInformationFunction(domainName, IP);
//Insert wait for objects to load here
setTimeout(function(){
// Done, go to next entry
processEntry();
}, 5000);
}, 5000);
}, 3000);
};
processEntry()
I would also recommend looking into promises and async/await, which can make this code look much neater:
function pause(milliseconds) {
return new Promise(function(finish){
setTimeout(finish, milliseconds)
})
}
async function processAllEntries(){
//For loop to add each domain and ip that is listed in the dictionary
for (var [domainName, IP] of Object.entries(namesAndAddresses)) {
window.self = document;
//Function to press sidebar "add device button"
MenuFunction();
//Insert wait for sub-page to load here
await pause(3000);
//Function to press "add device manually button" **THIS IS THE FUNCTION THAT RETURNS NULL**
AddDeviceFunction();
//Insert wait for objects to load here
await pause(5000);
//Function to add device information and then press the save button
AddDeviceInformationFunction(domainName, IP);
//Insert wait for objects to load here
await pause(5000);
}
}
Your pause causes the UI not to be able to render. You can make a function that uses await to look for an element. As soon as it is available it will update.
function addElem() {
const div = document.createElement('div');
div.className = 'foo';
div.textContent = 'hello';
document.body.append(div);
}
const waitForElem = (selector) => new Promise((resolve, reject) => {
const check = () => {
const elem = document.querySelector(selector);
if (elem) {
resolve(elem);
} else {
window.setTimeout(check, 1);
}
};
check();
});
window.setTimeout(addElem, 5000);
(async function() {
const el1 = await waitForElem('#bar');
el1.textContent = 'found 1';
const el2 = await waitForElem('.foo');
el2.textContent = 'found 2';
})();
<div id="bar">Test</div>
I have a web page where a javascript calculation in a function takes lot of time to finish and makes the page to freeze. What technique should I use to make sure the javascript does not freeze the browser when the calculation is happening in the background?
If you only need to do a calculation and don't need to access the DOM during the long running calculation, then you have two options:
You can break the calculation up into pieces and do a piece at a time on a setTimeout(). On each setTimeout() call, the browser will be free to serve other events and will keep the page alive and responive. When you finish the last piece of the calculation, you can then carry out the result.
You can run the calculation in the background using a webworker in modern browsers. When the calcuation is done in the webworker, it sends a message back to the main thread and you can then update the DOM with the result.
Here's a related answer that also shows an example: Best way to iterate over an array without blocking the UI
Let me elaborate on #jfriend00's answer by giving a concrete stripped down example. Here is a long-running JavaScript process that can be started by clicking a button. Once it runs, it freezes the browser. The process consists of a long loop that repeats some workload where one iteration takes comparatively little time.
Due to the browser freeze, debugging a script like this is not easy. One alternative to avoid browser freeze is using a web worker. The drawback of that approach is the poor debuggabilty of web workers per se: Tools like Firebug are not supported.
<html>
<head>
<script>
var Process = function(start) {
this.start = start;
}
Process.prototype.run = function(stop) {
// Long-running loop
for (var i = this.start; i < stop; i++) {
// Inside the loop there is some workload which
// is the code that is to be debugged
console.log(i);
}
}
var p = new Process(100);
window.onload = function() {
document.getElementById("start").onclick = function() {
p.run(1000000000);
}
}
</script>
</head>
<body>
<input id="start" type="button" value="Start" />
</body>
</html>
Using a Queue data structure (e.g. http://code.stephenmorley.org/javascript/queues/), an interval timer and some small modification to the control flow of the original process one can build a GUI that doesn't freeze the browser, leaves the process fully debuggable and even allows additional features
like stepping, pausing and stopping.
Here is how it goes:
<html>
<head>
<script src="http://code.stephenmorley.org/javascript/queues/Queue.js"></script>
<script>
// The GUI controlling process execution
var Gui = function(start) {
this.timer = null; // timer to check for inputs and/or commands for the process
this.carryOn = false; // used to start/pause/stop process execution
this.cmdQueue = new Queue(); // data structure that holds the commands
this.p = null; // process instance
this.start = start;
this.i = start; // input to the modified process
}
Gui.prototype = {
/**
* Receives a command and initiates the corresponding action
*/
executeCmd: function(cmd) {
switch (cmd.action) {
case "initialize":
this.p = new Process(this);
break;
case "process":
this.p.run(cmd.i);
break;
}
},
/*
* Places next command into the command queue
*/
nextInput: function() {
this.cmdQueue.enqueue({
action: "process",
i: this.i++
});
}
}
// The modified loop-like process
var Process = function(gui) {
this.gui = gui;
}
Process.prototype.run = function(i) {
// The workload from the original process above
console.log(i);
// The loop itself is controlled by the GUI
if (this.gui.carryOn) {
this.gui.nextInput();
}
}
// Event handlers for GUI interaction
window.onload = function() {
var gui = new Gui(100);
document.getElementById("init").onclick = function() {
gui.cmdQueue.enqueue({ // first command will instantiate the process
action: "initialize"
});
// Periodically check the command queue for commands
gui.timer = setInterval(function() {
if (gui.cmdQueue.peek() !== undefined) {
gui.executeCmd(gui.cmdQueue.dequeue());
}
}, 4);
}
document.getElementById("step").onclick = function() {
gui.carryOn = false; // execute just one step
gui.nextInput();
}
document.getElementById("run").onclick = function() {
gui.carryOn = true; // (restart) and execute until further notice
gui.nextInput();
}
document.getElementById("pause").onclick = function() {
gui.carryOn = false; // pause execution
}
document.getElementById("stop").onclick = function() {
gui.carryOn = false; // stop execution and clean up
gui.i = gui.start;
clearInterval(gui.timer)
while (gui.cmdQueue.peek()) {
gui.cmdQueue.dequeue();
}
}
}
</script>
</head>
<body>
<input id="init" type="button" value="Init" />
<input id="step" type="button" value="Step" />
<input id="run" type="button" value="Run" />
<input id="pause" type="button" value="Pause" />
<input id="stop" type="button" value="Stop" />
</body>
</html>
While this approach certainly doesn't fit all long-running scripts one can think of, it certainly
can be adapted to any loop-like scenario. I'm using it to port Numenta's HTM/CLA artificial
intelligence algorithms to the browser.
Some browsers have only one thread for running your code and updating the UI (in other words, until the calculation is complete, the browser will appear "frozen"). You'll want to try to perform the action asynchronously, in one way or another.
If the calculation is really expensive, you might want to make a call to the server and let the server do the calculation, and callback the client when the calculation is done.
If the calculation is kind of expensive, you can try to do it in chunks on the client. This isn't actually asynchronous (as the client will block while executing each chunk) but the goal is to make the chunks small enough that the blocking is not noticeable.
setTimeout(function() { ..code }, 0);
I recommend this for heavy execution time, and also for on load ajax you could try to add
$(window).on("load", function (e) { }); // for jquery v3
if its in the loading process.
I think this should resolve your problem,
function myClickOperation(){
var btn_savebutton2 = document.querySelector("input[id*='savebutton2']");
setTimeout(function () { btn_savebutton2.click() }, 1000);
}
// Full Html content
<html>
<script>
function myClickOperation(){
var btn_savebutton2 = document.querySelector("input[id*='savebutton2']");
document.getElementById('savebutton1').disabled = true;
setTimeout(function () { btn_savebutton2.click() }, 1000);
}
function testClick(){
var idd = document.getElementById("myid");
idd.innerHTML =idd.innerHTML +"<br/>" + new Date();
if(true){
setTimeout(function () { testClick() }, 1);
}
}
</script>
<body>
<input type="button" id="savebutton1" onclick="myClickOperation()" value="Click me" />
<input type="button" id="savebutton2" onclick="testClick()" value="Do not click this" />
<input type="text"/>
<input type="button" value="temp"/>
<div style="height: 300px;overflow-y: scroll;" id="myid"/>
</body>
My objective is to keep a user in a view as long as he/she keeps clicking a button within a certain lapse.
I'm using Rails and was exploring a solution via an embedded JS in the pertinent view.
So far I'm able to set a time after which the user will be redirected to root path with the following script:
var delayedRedirect = function (){
window.location = "/";
}
var delay = 10000;
$(document).ready(function() {
setTimeout('delayedRedirect()', delay);
});
I've been trying to write a function that resets the value of 'delay'or that calls the setTimeoutFunction again.
$('#btn-persist').click(function() {
delay = 3000;
// or calling again setTimeout('delayedRedirect()', delay);
});
But I noticed that changing the variable won't affect the setTimeout function that has already been called.
I've also tried to use the clearTimeout function as below without success
var delayedRedirect = function (){
window.location = "/persists";
}
var delay = 3000;
var triggerRedirect = function() { setTimeout('delayedRedirect()', delay);
}
var stopRedirect = function (){
clearTimeout(triggerRedirect);
}
$(document).ready(function() {
triggerRedirect();
$('#btn-persist').click(function() {
stopRedirect();
});
});
I wonder why this may not be working and if there's any other way to stop the execution of the setTimeout function that has already been called so I can call it again to effectively reset the time to the original value of 'delay'.
At the same time, I don't want to stop any other JS functions that are running in parallel.
Do you see a better solution to achieve this?
The main problem why clearTimeout is not working. because you are clearing a anonymous function instead of a setTimeout variable
change this
var triggerRedirect = function() { setTimeout('delayedRedirect()', delay);
}
to this
var triggerRedirect = setTimeout('delayedRedirect()', delay);
Edit:
also change this (if you want to restart the inactive redirect trigger)
$('#btn-persist').click(function() {
stopRedirect();
});
to this
$('#btn-persist').click(function() {
stopRedirect();
triggerRedirect();
});
I need to do the opposite of this post, "Best way to iterate over an array without blocking the UI"
I have to loop through hundreds of rows and set a value for each. But that job has to complete before I allow the users to do the next step and submit the updated rows to the database.
The javascript is below.
// toolbar events/actions
changeZeroDiscountButton.click(function (event) {
var value = discountComboBox.jqxComboBox('val');
if ((value != null) && (value != "")) {
value = value / 100;
// get all the rows (this may have to be changed if we have paging
var datainformations = $('#jqxgrid').jqxGrid('getdatainformation');
var rowscounts = datainformations.rowscount;
for (var i = 0; i < rowscounts; i++) {
var preSetDiscount = $("#jqxgrid").jqxGrid('getcellvalue', i, "discount");
if (preSetDiscount == .0000) {
$("#jqxgrid").jqxGrid('setcellvalue', i, "discount", value);
}
}
}
});
JavaScript is designed so it does not block the UI in any way, and this is one of its most important features for the browsers. The only exceptions are the popup message boxes (i.e. alert(), confirm(), and propmpt()). Even if it is possible, it's highly not recommended to block the UI.
There are many alternative ways to prevent the user from firing actions that shouldn't be fired until something else happens. Examples:
Disable the action's button until your processing ends then enable it back.
Set a flag (e.g. var processing = true) and check that flag in the click event of the action's button so it displays a message (e.g. "still processing, please wait...") when flag is true and execute the action when flag is false. Remember not to use alert() for the message otherwise you'll block the processing. Use a popup div instead.
Set the event handler at the beginning of the processing to a function that displays a message (e.g. "still processing, please wait...") and at the end of the processing, set the event handler to the function that will do the action. Remember not to use alert() for the message otherwise you'll block the processing. Use a popup div instead.
Show a modal popup div at the beginning of the processing with a message (e.g. "still processing, please wait..."), or progress bar, or some animation. The modal popup prevents the user from interacting with the page so they cannot click anything. For that to work, the modal popup must not have a close button or any other way to close it. At the end of processing, close the modal popup so the user can now continue.
Important Note: You mentioned in your comment to the other answer that the overlay (which is similar to the modal popup in my last point) is not displayed until the end of processing. That's because your processing is occupying the processor and preventing it from handling the UI thread. When you can do is delay your processing. So first display the modal popup (or overlay), then use setTimeout() to start processing 1 second later (maybe 500 millisecond or even less is enough). This gives the processor enough time to handle the UI thread before it starts your long processing.
Edit Here is an example of the last method:
function start() {
disableUI();
setTimeout(function() {
process();
}, 1000);
}
function process() {
var s = (new Date()).getTime();
var x = {};
for (var i = 0; i < 99999; i++) {
x["x" + i] = i * i + i;
}
var e = new Date().getTime();
$("#results").text("Execution time: " + (e - s));
enableUI();
}
function disableUI() {
$("#uiOverlay").dialog({
modal: true,
closeOnEscape: false,
dialogClass: "dialog-no-close",
});
}
function enableUI() {
$("#uiOverlay").dialog("close");
}
.dialog-no-close .ui-dialog-titlebar {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css">
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
<button type="button" onclick="start()">Start</button>
<div id="results"></div>
<div id="uiOverlay" style="display: none;">Processing... Please wait...</div>
Edit 2 Here is an example of the third method:
$("#StartButton").on("click", start);
function start() {
//remove all previous click events
$("#StartButton").off("click");
//set the click event to show the message
$("#StartButton").on("click", showProcessingMsg);
//clear the previous results
$("#results").text("");
setTimeout(function() {
process();
}, 1000);
}
function process() {
var s = (new Date()).getTime();
var x = {};
for (var i = 0; i < 99999; i++) {
x["x" + i] = i * i + i;
}
var e = new Date().getTime();
$("#results").text("Execution time: " + (e - s));
//remove all previous click events
$("#StartButton").off("click");
//set the click event back to original
$("#StartButton").on("click", start);
}
function showProcessingMsg() {
$("#results").text("Still processing, please wait...");
}
.dialog-no-close .ui-dialog-titlebar {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button type="button" id="StartButton">Start</button>
<div id="results"></div>
If it is a long loop browser by itself will block all other events. You may just experience a freeze browser.
That wont be a good experience.
You can look cover the UI with an Overlay like this and inform user about the operation
I've got a search input which sends data from an input to a php file as I type. The php file does a search on my database and shows up a list of search options. You know, the ajax style live searching.
My problem is, if you type something really fast, it might just conduct a search off of the first 1 or 2 letters even though another 10 have been typed. This causes a few problems.
My jQuery looks a bit like this:
$(document).ready(function(){
$('#searchMe').keyup(function(){
lookup(this.value);
});
});
and
function lookup(searchinput) {
if(searchinput.length == 0) {
// Hide the suggestion box.
$("#suggestions").hide();
} else {
$('#loading').fadeIn();
$.post("/RPCsearch.php", {queryString: ""+searchinput+""}, function(data){
if(data.length > 0) {
$("#suggestions").html(data).show();
$('#loading').fadeOut();
}
});
}
} // lookup
So I'm just curious, how can I make it so that my script waits until I've finished typing before running the function? My logic says something like if a key hasn't been pressed for 200 micro seconds, run the function, otherwise hold up a bit.
How is this done?
Easy, using setTimeout. Of course you only want one timer going at once, so it's important to use clearTimeout at the beginning of the function...
$(function() {
var timer;
$("#searchMe").keyup(function() {
clearTimeout(timer);
var ms = 200; // milliseconds
var val = this.value;
timer = setTimeout(function() {
lookup(val);
}, ms);
});
});
You may be interested in my bindDelayed jQuery mini-plugin. It:
Allows you to specify a delay before kicking off the request
Automatically cancels any previous requests that were scheduled to go off
Automatically cancels any in-air XHR requests that were in progress when you make your request
Only invokes your callback for the latest request
If the user types "s", waits long enough for the request to go out, and then types "a", and the response for "s" comes back before the response for "sa" you won't have to deal with it.
The answer to the original question using bindDelayed would look like so:
// Wait 200ms before sending a request,
// avoiding, cancelling, or ignoring previous requests
$('#searchMe').bindDelayed('keyup',200,'/RPCsearch.php',function(){
// Construct the data to send with the search each time
return {queryString:this.value};
},function(html){
// Use the response, secure in the knowledge that this is the right response
$("#suggestions").html(html).show();
},'html','post');
In case my site is down, here's the plugin code for Stack Overflow posterity:
(function($){
// Instructions: http://phrogz.net/jquery-bind-delayed-get
// Copyright: Gavin Kistner, !#phrogz.net
// License: http://phrogz.net/js/_ReuseLicense.txt
$.fn.bindDelayed = function(event,delay,url,dataCallback,callback,dataType,action){
var xhr, timer, ct=0;
return this.on(event,function(){
clearTimeout(timer);
if (xhr) xhr.abort();
timer = setTimeout(function(){
var id = ++ct;
xhr = $.ajax({
type:action||'get',
url:url,
data:dataCallback && dataCallback(),
dataType:dataType||'json',
success:function(data){
xhr = null;
if (id==ct) callback.call(this,data);
}
});
},delay);
});
};
})(jQuery);
You really ought to look at using the jQuery autocomplete plugin. I find this plugin to be very useful and it already does what you need. Look particularly at the delay option, which you can customize to change how long the plugin waits after a keystroke to run.
1 solution in psuedocode:
OnKeyPress()
txt = getTxt
sleep(200)
newTxt = getTxt
if (txt == newTxt) // nothing has been typed so do something
run my thing
this one is happy
$(document).ready(function(){
$("#searchMe").keyup(function () {
try{window.clearTimeout(timeoutID);}catch(e){}
timeoutID = window.setTimeout(run, 2000); //delay
function run()
{ //dowhatev
var text = $("#searchMe").val();
//$("#showit").html(text);
}
});
});
I have found the best success when attaching the event to keypress, keydown, and keyup inputs. Safari/FireFox/IE all seem to handle special keypresses (delete, backspace, etc.) a bit differently but using all events together seems to cover it. The only way that running all events works though is to use setTimeout so that when they all fire it just resets the timer and ultimately the callback only gets executed once.
var delay = 200;
var search_timer = null;
$("#searchMe").keydown(function(e) {
if(search_timer) {
clearTimeout(search_timer);
}
search_timer = setTimeout(lookup, delay);
});
$("#searchMe").keypress(function(e) {
if(search_timer) {
clearTimeout(search_timer);
}
search_timer = setTimeout(lookup, delay);
});
$("#searchMe").keyup(function(e) {
if(search_timer) {
clearTimeout(search_timer);
}
search_timer = setTimeout(lookup, delay);
});