I have an asynchronous function running in my web application that enables a chat input. In a different component I need to set a variable to the input and then focus() on it when certain conditionals are met. Unfortunately the chat input DOM element isn't always available when I try to declare it based on the asynchronous nature of the function that enables it. Being familiar with how setTimeoout() works with the call stack I wrapped my declaration in a setTimeout and everything (seemingly) works as expected now.
So my question is if this is a good practice or not? I'm using React/Redux and will have to do a lot of prop threading and extra logic to get a seemingly easy task accomplished without the setTimeout.
It is an alright practice ;)
It gets the job done, but it is usually preferable to work with callbacks or promises instead of polling to see if the dom is ready. The main failing with a "setTimeout" approach is that you are setting a timer and what if the resource (chat plugin) takes longer to load than the timer you set.
// Run it
main();
// Supporting code
function main() {
let attempts = 0;
const maxAttempts = 10;
tryUpdate();
function tryUpdate() {
// Call it once
attempts++;
const success = updateAndFocus();
console.log(attempts);
// Keep calling it every 100ms
if (!success && attempts < maxAttempts) {
setTimeout(() => tryUpdate(), 100);
}
}
}
function updateAndFocus() {
const el = document.getElementById('findme');
if (!el) return false;
// do work
el.focus;
return true;
}
Related
I've a question/problem with an whileloop
I need to wait until something changes outside the while loop.
Let's say i have this while loop:
window.changeMe = true;
while(window.changeMe){
}
now i have these two options:
Change the changeMe variable via the Console/JavaScript Execution
Change the changeMe variable via an WebSocket Event
but neither is working, if i change the Variable directly, it is not changed.
If i trigger an WebSocket Event its not getting called.
Maybe its BLOCKED.. so is there any other way to change the variable?
I known i can use await and its already working that way, but the problem is that these functions with while are called via an Addon
and using many await's looks kinda ugly for the addon creator :(
an system with setTimeout & Callbacks are also working but also looks kinda ugly..
Yes, you are correct. Having a infinite while loop will prevent executing any other code from javascript event loop which occupies the main thread.
In order to imitate the same behavior you can implement your own while loop that is friendly to asynchronous events and external code execution. You have to use:
tail recursion in order to minimize the memory footprint,
setTimeout as a mechanism to allow other parts of your code to run asynchronously.
EXAMPLE:
window.changeMe = true;
let stop = setTimeout(() => { console.log("External change stop"); window.changeMe = false; }, 4000)
var whileLoop = () => {
console.log("Inside: ", window.changeMe)
return window.changeMe
? setTimeout(() => { whileLoop(); }, 0)
: false
}
whileLoop()
console.log("Outside: ", window.changeMe)
Here is a fiddle:
https://jsfiddle.net/qwmosfrd/
Here is a setInterval fiddle:
https://jsfiddle.net/2s6pa1jo/
Promise return value example fiddle:
https://jsfiddle.net/0qum6gnf/
JavaScript is single-threaded. If you have while (true) {}, then nothing else outside the while loop can change the state of your program. You need to change your approach. You probably want to set up event listeners instead or put this inside an async function so you can use await to release execution, or some other asynchronous API. But plain vanilla while () {} is synchronous and cannot be affected by other things while it is running.
You can't use a while loop in that way in nodejs.
Nodejs runs your Javascript in a single thread and the overall architecture of the environment is event driven. What your while loop is doing is a spin loop so while that loop is running, no other events can ever run. You have to return control back to the event loop before any other events can run. That means that timers, network events, etc... cannot run while your spin loop is running. So, in nodejs, this is never the right way to write code. It will not work.
The one exception could be if there was an await inside the loop which would pause the loop and allow other events to run.
So, while this is running:
while(window.changeMe){
}
No other events can run and thus nothing else gets a chance to change the changeMe property. Thus, this is just an infinite loop that can never complete and nothing else gets a chance to run.
Instead, you want to change your architecture to be event driven so that whatever changes the changeMe property emits some sort of event that other code can listen to so it will get notified when a change has occurred. This can be done by having the specific code that changes the property also notify listeners or it can be done by making the property be a setter method so that method can see that the property is being changed and can fire an event to notify any interested listeners that the value has changed.
I have read from multiple places that setTimeout() is preferable to setInterval() when setting something up to basically run forever. The code below works fine but after about an hour of running Firefox (38.0.1) throws an error of too much recursion.
Essentially I have it grabbing a very small amount of text from counts.php and updating a table with that information. The whole call and return takes about 50ms according to the inspectors. I'm trying to have it do this every x seconds as directed by t.
I suspect if I switch to setInterval() this would probably work, but I wasn't sure what the current state of the setTimeout() vs setInterval() mindset is as everything I've been finding is about 3-5 years old.
$(document).ready(function() {
t = 3000;
$.ajaxSetup({cache: false});
function countsTimer(t) {
setTimeout(function () {
$.getJSON("counts.php", function (r) {
$(".count").each(function(i,v) {
if ($(this).html() != r[i]) {
$(this).fadeOut(function () {
$(this)
.css("color", ($(this).html() < r[i]) ? "green" : "red")
.html(r[i])
.fadeIn()
.animate({color: '#585858'}, 10000);
})
};
});
t = $(".selected").html().slice(0,-1) * ($(".selected").html().slice(-1) == "s" ? 1000 : 60000);
countsTimer(t);
});
}, t);
};
countsTimer(t);
});
Update: This issue was resolved by adding the .stop(true, true) before the .fadeOut() animation. This issue only occurred in Firefox as testing in other browsers didn't cause any issues. I have marked the answer as correct in spite of it not being the solution in this particular case but rather it offers a good explanation in a more general sense.
You should indeed switch to setInterval() in this case. The problem with setInterval() is that you either have to keep a reference if you ever want to clear the timeout and in case the operation (possibly) takes longer to perform than the timeout itself the operation could be running twice.
For example if you have a function running every 1s using setInterval, however the function itself takes 2s to complete due to a slow XHR request, that function will be running twice at the same time at some point. This is often undesirable. By using setTimout and calling that at the end of the original function the function never overlaps and the timeout you set is always the time between two function calls.
However, in your case you have a long-running application it seems, because your function runs every 3 seconds, the function call stack will increase by one every three seconds. This cannot be avoided unless you break this recursion loop. For example, you could only do the request when receiving a browser event like click on the document and checking for the time.
(function()
{
var lastCheck = Date.now(), alreadyRunning = false;
document.addEventListener
(
"click",
function()
{
if(!alreadyRunning && Date.now() - lastCheck > 3000)
{
alreadyRunning = true;
/* Do your request here! */
//Code below should run after your request has finished
lastCheck = Date.now();
alreadyRunning = false;
}
}
)
}());
This doesn't have the drawback setInterval does, because you always check if the code is already running, however the check only runs when receiving a browser event. (Which is normally not a problem.) And this method causes a lot more boilerplate.
So if you're sure the XHR request won't take longer than 3s to complete, just use setInterval().
Edit: Answer above is wrong in some aspects
As pointed out in the comments, setTimeout() does indeed not increase the call stack size, since it returns before the function in the timeout is called. Also the function in the question does not contain any specific recursion. I'll keep this answer because part of the question are about setTimeout() vs setInterval(). However, the problem causing the recursion error will probably be in some other piece of code since there is not function calling itself, directly or indirectly, anywhere in the sample code.
Is there anything like a "dispose" function or "thread shutdown hook" for a specific thread running via setInterval?
(function () {
var _setInterval = window.setInterval,
_clearInterval = window.clearInterval;
window.setInterval = function (fn, time) {
//Implement your hooks here, hopefully without side effects
return _setInterval(fn, time);
};
window.clearInterval = function (id) {
//Implement your hooks here, hopefully without side effects
return _clearInterval(id);
};
})()
From comments it became clear that you don't need hooking because you are in an environment that you control. In that case you can just write wrapper functions like myClearInterval etc. with same principle.
First off, as others have said, there are no threads in javascript (other than WebWorkers, but I don't think that's what you're talking about here.
All setInterval() does is call a function on a repeated basis - it isn't a thread, it isn't pre-emptive and it won't get called until all other javascript has stopped executing so the timer event can be processed. If your issue is that you want to dispose of some state when the interval is cleared so it will no longer be called, then you have two options:
1) You can use a javascript closure to store your state and when the interval is cleared, the closure will automatically be released.
2) You can create your own version of clearInterval that both clears the interval timer and cleans up your state.
The javascript closure option would look like this:
var interval;
function startMyInterval() {
// sample state variables
var state1 = 0;
var state2 = [];
var state3 = {whatever: "whatever"};
interval = setInterval(function() {
// javascript code here that can reference state1, state2 and state3
}, 1000);
}
// then some time later when you want to stop the interval, you call clearInterval
// the closure is released and all the state variables are freed automatically
clearInterval(interval);
Or, if you want to do any other things when the interval is cleared, you can make your own function for clearing the interval that will not only release the closure, but also let you run any other code.
function clearMyInterval() {
clearInterval(interval);
// do any other cleanup you want to when the interval is stopped
}
I see that others have suggested hooking/replacing window.clearInterval() with your own function, but I prefer not to do that because it is unclear if that is a supported/documented feature and some system functions (more and more of them over time) are becoming protected so they cannot be replaced.
I've got a sequence of Javascript function calls in a function I have defined to be executed when a web doc is ready. I expected them to be executed in sequence, as one ends the next begins, but the behaviour I see doesn't match up with that.
Additionally there is manipulation of the graphical components going on in between the calls (for example, I add in a checkpoint time to draw on a div on the page inbetween each of the mentioned calls) but those redraws aren't happening in sequence... they all happen at once.
I'm a bit of a n00b with the whole javascript-in-the-browser thing, is there an obvious mistake I'm making, or a good resource to go find out how to do this stuff?
Update - sample
// called onReady()
function init() {
doFirstThing();
updateDisplayForFirstThing();
doSecondThingWithAjaxCall();
updateDisplayForSecondThing();
...
reportAllLoaded();
}
IE won't update the display until the current script is finished running. If you want to redraw in the middle of a sequence of events, you'll have to break your script up using timeouts.
If you post some code we can help refactor it.
edit: here's a general pattern to follow.
function init() {
doFirstThing();
updateDisplayForFirstThing();
}
function updateDisplayForFirstThing() {
// existing code
...
// prepare next sequence
var nextFn = function() {
// does this method run async? if so you'll have to
// call updateDisplayForSecondThing as a callback method for the
// ajax call rather than calling it inline here.
doSecondThingWithAjaxCall();
updateDisplayForSecondThing();
}
setTimeout(nextFn, 0);
}
function updateDisplayForSecondThing() {
// existing code
...
// prepare next sequence
var nextFn = function() {
// continue the pattern
// or if you're done call the last method
reportAllLoaded();
}
setTimeout(nextFn, 0);
}
This can be fixed for many cases by using callbacks, especially with AJAX calls -- for example:
function doFirstThing(fn){
// doing stuff
if(typeof fn == 'function') fn();
}
function updateDisplayForFirstThing(){
// doing other stuff
}
function init(){
doFirstThing(updateDisplayForFirstThing);
}
Another option is to use return values:
function doFirstThing(fn){
// doing stuff
if(x) return true;
else return false;
}
function updateDisplayForFirstThing(){
// doing other stuff
return true;
}
function init(){
if(doFirstThing()){ updateDisplayForFirstThing(); }
}
setting timeouts to step through your code is not really a good way to fix this problem because you'd have to set your timeouts for the maximum length of time each piece of code could possibly take to execute.
However, you may still sometimes need to use a setTimeout to ensure the DOM has properly updated after certain actions.
If you end up deciding that you would like some JavaScript threading, check out the still being drafted Web Workers API. Browser support is hit and miss though the API is implemented in most modern web browsers.
Question: exactly how did you go about determining when the "doc is ready"? The DOMContentLoaded event isn't supported in IE I'm fairly certain... if you're in need of waiting for your document to load in its entirety you could use something like this:
var onReady = function(callback) {
if (document.addEventListener) {
document.addEventListener("DOMContentLoaded", callback, false);
return true;
} else if (document.attachEvent) {
var DOMContentLoaded = function() {
if (document.readyState === "complete") {
document.detachEvent("onreadystatechange", DOMContentLoaded);
onReady();
}
};
return true;
}
};
Then of course you'll need to develop a setTimeout testing for some flags state indicating the page is loaded upon completion before continuing the execution of the rest of your code... that or any number of other methods...
Or you could just include the script at the bottom of your body...
I'm just rambling though until you have some code to show us?
There has to be an easy way to do this, but I'm new to JS.
I have a javascript program that (1) takes user input, (2) updates the webpage based on that input, then (3) performs a lengthy calculation. The trouble is that the webpage doesn't register the update till after the lengthy calculation. Isn't there a way to pause execution so that the page can update before the long calculation?
I've tried setTimeout and window.setTimeout, but they made no difference.
The program is for playing a game: the user inputs a move, the script updates the position, then calculates its next move. postMessage prints text messages using div.innerHTML; buttonFn takes the input from the user, updates the position, prints a message, then starts the computer calculating.
function buttonFn(arg){
var hst = histButt;
hst.push(arg);
var nwmv = hst.clone();
postMessage("New move: " + nwmv.join());
if(status == opposite(comp) && !pauseQ){
var mvsposs = movesFromPos(posCur,status);
if(mvsposs.has(nwmv)){
updatePosCur(nwmv);
//waitasec();
if(comp == status && !pauseQ){
compTurn();
};
}
else{
histButt = nwmv;
};
};
};
yes there is, call your function like this. Using setTimeout will allow a page reflow prior to your JS executing.
function buttonFn(arg){
var hst = histButt;
hst.push(arg);
var nwmv = hst.clone();
postMessage("New move: " + nwmv.join());
if(status == opposite(comp) && !pauseQ){
var mvsposs = movesFromPos(posCur,status);
if(mvsposs.has(nwmv)){
updatePosCur(nwmv);
//waitasec();
if(comp == status && !pauseQ){
setTimeout(function(){
compTurn();
},0);
};
}
else{
histButt = nwmv;
};
};
};
Remember, JS is very event driven friendly. If you can move things off, and call them later do it. Thats the only way we can support multi-threaded like behavior.
setTimeout
If you only need to support modern browsers (or if you use a transpiler), you can now use ES6 features to make this much easier and more in the style the original questioner was trying to do. (I realize the question is 8 years old - no harm in a new, more current answer!)
For example you can do something like this:
// helper function to use a setTimeout as a promise.
function allowUpdate() {
return new Promise((f) => {
setTimeout(f, 0);
});
}
// An infinitely looping operation that doesn't crash the browser.
async function neverStopUpdating(someElement) {
let i = 0;
while (true) {
someElement.innerText = i;
i++;
await allowUpdate();
}
}
If you're trying to do a hard computation you'll want to make sure not to do this await too frequently - in this example, in Chrome at time of writing, i only increments by about 150 per second because the context switch of a setTimeout is not fast (where you'd get hundreds of thousands in a second if you didn't yield for updates). You'd likely want to find a balance, either always perform some number of iterations before allowing an update, or maybe eg. call Date.now() in your loop and yield for an update whenever 100ms have passed since the last time you allowed an update.
You can do the update, wait for a bit of time, than do the calculation.
OR
You can use webworkers on browsers that support them.
Without having actual code, that is the best answer that I can give you.
JavaScript is single threaded. If you do your calc server side you could get the results via ajax which is called asynchronously, not blocking your ui.