I'm reading a book called Pro Javascript Techinques(John Resig)
a piece of code of the core-animation is as follwing
setTimeout(function () {
elem.style['width'] = (i / 100) * fullwith + 'px';
}, **(i + 1) * 10** );
I wonder while it should change the 'each-timeout' time to implement it
why the 'each-timeout' time should not be the same,eg:1000,It flash consequently
Thanks so much~
Each setTimeout registers a new timeout (all starting "now") that will fire 10ms apart, thus you do get a regular pulse every 10ms, but you queue all of them up in advance — the first timeout doesn't fire until 10ms after you've registered all of these callbacks.
Why he should do it that way when there's setInterval, I don't know — maybe interesting to find out, is it in the book?
Related
I am currently trying to get a repeating sound effect, which is getting slower over time with setTimeout() in sync with an animation. As soon as I get it in sync it will work and stay in sync for the time I am working on the program. But now when I was away for about 1 1/2 hours and run the program again exactly as I left it, the sound is no longer in sync with the animation. The same thing happend to me with the same program yesterday when I was away for some time and overnight.
So I was thinking that setTimeout() is somehow working with the current time and will work differently at different times. Can someone confirm this?
Here is my code for reference.
The timeout function:
const timeoutRollSound = (time = 0, index = 0) => {
setTimeout(() => {
const audioClick = new Audio(
"foo/bar.wav"
);
audioClick.play();
index++;
timeoutRollSound(0.05 * Math.pow(index, 2) + 3 * index - 50, index)
}, time);
};
The animation:
$(".itemToAnimate").animate(
{ right: endpoint },
{
duration: 10000,
easing: "easeOutQuint",
}
);
I had this issue in Java years ago. Here's what's going on.
When you set a timeout (like you are doing) you are actually saying "I don't want this function to execute before X milliseconds". So the timeout function may be ready to run, but JavaScript or the browser might be doing something else.
setInterval might work better. But the other thing you can do is include the difference between when the code was eligible to be run and the time it was actually run at, like:
setTimeout(() => {
const audioClick = new Audio(
"foo/bar.wav"
);
audioClick.play();
index++;
timeoutRollSound(0.05 * Math.pow(index, 2) + 3 * index - 50, index)
timeoutRollSound.last = Date.now();
}, time - ((Date.now - timeoutRollSound.last) );
This reminds me of an issue I was having with another JS library and could be related. If you put the tab in browser to the background, the execution will be suspended. From what I'm getting from your code, you rely on the fact that the recursion setTimeout will run constantly which could be the source of your issues.
This could be the issue you are having, take a look: Chrome: timeouts/interval suspended in background tabs?
I watched Jake Archibald's talk about event loop - https://vimeo.com/254947206. Based on the talk my understanding was that event loop will execute as many macro tasks as it can fit in one frame and if there is some long running macro task it will cause frames to be skipped. So my expectation was that any task, running longer then usual frame duration, would cause other tasks to be executed in the next frame. I tested that by creating one button and multiple handlers like this https://codepen.io/jbojcic1/full/qLggVW
I noticed that even though handlerOne is long running (due to calculating computationally intensive fibonacci), handlers 2, 3 and 4 are still executed in the same frame. Only timeoutHandler is being executed in the next frame. Here are the logs I am getting:
animationFrameCallback - 10:4:35:226
handler one called. fib(40) = 102334155
handler two called.
handler three called.
handler four called.
animationFrameCallback - 10:4:36:37
timeout handler called
animationFrameCallback - 10:4:36:42
so the question is why are handlers two, three and four executed within the same frame as handler one?
To make things even more confusing according to https://developer.mozilla.org/en-US/docs/Web/API/Frame_Timing_API,
A frame represents the amount of work a browser does in one event
loop iteration such as processing DOM events, resizing, scrolling,
rendering, CSS animations, etc.
and to explain "one event loop iteration" they linked https://html.spec.whatwg.org/multipage/webappapis.html#processing-model-8 where it's stated that in one iteration:
one macro task is processed,
all micro tasks are processed
rendering is updated
... (there are some other steps too which are
not important for this)
which doesn't seem to be correct at all.
You are mixing a few concepts here.
The "frame" you are measuring in your codepen is the one of the step 10 - Update the rendering.
Quoting the specs:
This specification does not mandate any particular model for selecting rendering opportunities. But for example, if the browser is attempting to achieve a 60Hz refresh rate, then rendering opportunities occur at a maximum of every 60th of a second (about 16.7ms). If the browser finds that a browsing context is not able to sustain this rate, it might drop to a more sustainable 30 rendering opportunities per second for that browsing context, rather than occasionally dropping frames. Similarly, if a browsing context is not visible, the user agent might decide to drop that page to a much slower 4 rendering opportunities per second, or even less.
So it is not sure at which frequency will this "frame" fire, but generally it is at 60FPS (most monitors refresh at 60Hz), so in this lapse of time, a lot of event loops iterations will normally occur.
Now, requestAnimationFrame is even more special in that it can discard frames if the browser thinks it has too much things to perform. So your fibonacci will most probably delay any execution of rAF callbacks until it's done.
What the MDN article you linked talks about is a "frame" in the realm of the PerformanceFrameTiming API. I must admit directly that I don't have a lot of knowledge about this particular API, and given its very limited browser support, I don't think we should spend too much time on it, except to say that this has nothing to do with a painting frame.
I think the most precise tool we have currently for measuring an EventLoop iteration is the Messaging API.
By creating a self-calling message event loop, we can hook to every EventLoop iterations.
let stopped = false;
let eventloops = 0;
onmessage = e => {
if(stopped) {
console.log(`There has been ${eventloops} Event Loops in one anim frame`);
return;
}
eventloops++
postMessage('', '*');
};
requestAnimationFrame(()=> {
// start the message loop
postMessage('', '*');
// stop in one anim frame
requestAnimationFrame(()=> stopped = true);
});
Let's see how your code behaves at a deeper level:
let done = false;
let started = false;
onmessage = e => {
if (started) {
let a = new Date();
console.log(`new EventLoop - ${a.getHours()}:${a.getMinutes()}:${a.getSeconds()}:${a.getMilliseconds()}`);
}
if (done) return;
postMessage('*', '*');
}
document.getElementById("button").addEventListener("click", handlerOne);
document.getElementById("button").addEventListener("click", handlerTwo);
document.getElementById("button").addEventListener("click", handlerThree);
document.getElementById("button").addEventListener("click", handlerFour);
function handlerOne() {
started = true;
setTimeout(timeoutHandler);
console.log("handler one called. fib(40) = " + fib(40));
}
function handlerTwo() {
console.log("handler two called.");
}
function handlerThree() {
console.log("handler three called.");
}
function handlerFour() {
console.log("handler four called.");
done = true;
}
function timeoutHandler() {
console.log("timeout handler called");
}
function fib(x) {
if (x === 1 || x === 2) return 1
return fib(x - 1) + fib(x - 2);
}
postMessage('*', '*');
<button id="button">Click me</button>
Ok, so there is actually one frame as in EventLoop iteration to fire between the event handlers and the setTimeout callback. I like it better.
But what about that "long running frames" thing we heard about?
I'll guess you are talking about the "spin the event loop" algorithm, which is indeed meant to allow the event loop to not block all the UI in some circumstances.
First, specs only tell the implementers that it is a recommendation to enter this algorithm for long running scripts, it is not a must.
Then, this algorithm is to allow the normal EventLoop processing of events registration and UI updates, but anything related to javascript is simply resumed at the next EventLoop iteration.
So there is actually no way from js to know if we did enter this algorithm.
Even my MessageEvent driven loop can't tell, because the event handler will just get pushed to after we exit this long-running script.
Here is an attempt to put in a more graphical way, at the risk of being technically inacurate:
/**
* ...
* - handle events
* user-click => push([cb1, cb2, cb3]) to call stack
(* - paint if needed (may execute rAF callbacks if any))
*
* END OF LOOP
—————————————————————————
* BEGIN OF LOOP
*
* - execute call stack
* cb1()
* schedule `timeoutHandler`
* fib()
* ...
* ...
* ...
* ... <-- takes too long => "spin the event loop"
* [ pause call stack ]
* - handle events
(* - paint if needed (but do not execute rAF callbacks))
*
* END OF LOOP
—————————————————————————
* BEGIN OF LOOP
*
* - execute call stack
* [ resume call stack ]
* (*fib()*)
* ...
* ...
* cb2()
* cb3()
* - handle events
* `timeoutHandler` timed out => push to call stack
(* - paint if needed (may execute rAF callbacks if any) )
*
* END OF LOOP
—————————————————————————
* BEGIN OF LOOP
*
* - execute call stack
* `timeoutHandler`()
* - handle events
...
*/
The answer actually existed in https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model
Key points:
The "frame" in "A frame represents the amount of work a browser does in one event loop iteration such as processing DOM events, resizing, scrolling, rendering, CSS animations, etc" is actually an event processing iteration, i.e. all the steps of https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model
Your meaning of "paint frame" is the step 11 "Update the rendering" part.
When to create a new "paint frame" in a event iteration is determined by the browser:
This specification does not mandate any particular model for selecting rendering opportunities. But for example, if the browser is attempting to achieve a 60Hz refresh rate, then rendering opportunities occur at a maximum of every 60th of a second (about 16.7ms). If the browser finds that a browsing context is not able to sustain this rate, it might drop to a more sustainable 30 rendering opportunities per second for that browsing context, rather than occasionally dropping frames. Similarly, if a browsing context is not visible, the user agent might decide to drop that page to a much slower 4 rendering opportunities per second, or even less.
So it is possible that a new "paint frame" is created after many event iteration(event/task processing).
For the long task, again, it is also possible that the browser decides not to create a new "paint frame".(Maybe it decides to handle these event immediately after each other or it is unnessary to create because the view content does not change).
Say I have 20 rows of JS code and I want the interpreter to execute only half of the code (<11 rows), then stop, without functions and returns, or without commenting the rest of the code (I already tried a return, see in advance).
A location.reload(true); in row 10 is a close solution but I'm looking for a client side stop.
My question
Is there like a stop command (or functionality) in JavaScript, that asks the interpreter to stop and behave as if no code ran so far?
Why I ask
The background for this question is a problem I have calling a function in more than one keydown event.
Given the keydown event is triggered only once, I consider sending the interpreter back to the start after the keydown event was triggered disposably, and without refreshing the page (Sorry if it seems absurd, I'm new to JS and failed finding the source of the bug).
Of course, the above question is different than the question "why does the keydown event triggered only once", which I already asked here - here's a link for context.
Preventing an XY problem
On one hand, I want to make sure there is no XY problem. On the other hand, I am not allowed to copywrite the previous question to this session hence linked to it above.
Either way, I would be glad to know if what I just described (client side stop of a JS interpreter) is even possible in the current release of the language.
Note: I decided to carefully rewrite the question after some comments earlier today (there were no answers) and did my best ensuring the question is informative and communal.
There is no stop command, but I experienced the need of it before when there was a long-running client-side operation.
The solution:
1) Divide the problem into small packets
2) Make sure you are able to make your function work only for activeMilliseconds milliseconds:
function doStuff(packets, currentIndex, activeMilliseconds) {
var start = new Date(); //Start of chunk
while((currentIndex < packets.length) && (new Date() - start < activeMilliseconds)) {
//Do something with packets[currentIndex]
currentIndex++;
}
return currentIndex;
}
3) Now that we are able to work for activeMilliseconds milliseconds, we need to use this asynchronously:
//Define packets
var currentIndex = 0;
var intervalID = setTimeout(function() {
If(currentIndex = doStuff(packets, currentIndex, activeMilliseconds) >= packets.length) clearInterval(intervalID);
}, totalMilliseconds);
Node: totalMilliseconds > activeMilliseconds should be true. For example, if totalMilliseconds is 250, and activeMilliseconds is 200, then in each 250 milliseconds a chunk will run for 200 milliseconds, leaving the browser to do its stuff for 50 milliseconds every 250 milliseconds even if there is a lot of work to do.
4) Make sure a job stops a previous similar job:
function doJob(packets, intervalID, activeMilliseconds, totalMilliseconds) {
clearInterval(intervalID);
//Define packets
var currentIndex = 0;
var intervalID = setTimeout(function() {
If(currentIndex = doStuff(packets, currentIndex, activeMilliseconds) >= packets.length) clearInterval(intervalID);
return intervalID;
}, totalMilliseconds);
}
If you use this idea for your key event, then it will stop the previous keyboard, your maximum wait time to do so will be activeMilliseconds, which is an acceptable compromise in my opinion.
That said, this methodology should be only used in the case when you have no other option. You need to know that Javascript has a single thread, so even if you trigger a function execution while a previous instance of the event is still running, your new event will sequentially be executed when the other event is finished.
I want to use a timer as a fallback in case I end up in an infinite loop. It seems that set interval is the right way to do this. However, it's not working for me.
From my research, it seems like setInterval should run in a separate thread in the background, but I don't see it.
Why is this behavior happening? And how do I solve this?
var time = 0;
window.setInterval(function(){time++;}, 1000);
while (true) {
//stuff done
if (time >= 5) {
break;
}
}
Browser javascript runs in a single thread. So if you perform something that takes too long - it will freeze browser.
See John Resig article for further details: http://ejohn.org/blog/how-javascript-timers-work/
After you read that article you'll get that your setInterval callback queued to be run in 1000ms after now but only after the current code is finished. It cannot finish though, because of the infinite loop.
zerkms has the correct answer. But I would add that web workers are a way to get some multi-threaded-ish behavior from client side javascript.
var worker = new Worker('my_task.js');
worker.onmessage = function(event) {
console.log("Called back by the worker!\n");
};
The worker runs in a background thread, and you can exchange messages and subscribe to events. It's pretty nifty.
As has been already said - the callback to setInterval doesn't run until the infinite loop finishes. To do what you are trying to achieve - without using web workers - you have to check the time from the loop itself:
var start = Date.now();
while((Date.now() - start) < 5000){
...
}
It seems that everyone has a few problems with clearInterval. I have built a slider that allows people to hover a click on arrows. The banner also rotates ever few seconds. I want to be able to have the auto-rotate turn off after someone clicks on one of the arrows.
Here's my code:
$(function(){
var intvl = 0;
intvl = setInterval(heroTransitionNext, 2000);
$('.rightArrow').click(function(){
window.clearInterval(intvl);
});
});
EDIT:
Here is the function it is calling:
function heroTransitionNext() {
$('.HP-hero li').filter(':visible').fadeOut('normal', function () {
if ($(this).next().length != 0) {
activeZone = parseInt(activeZone) + 1;
$(this).next().fadeIn('normal', heroNavHighlight(activeZone));
} else {
activeZone = 1;
$('.HP-hero li:first-child').fadeIn('normal', heroNavHighlight(activeZone));
}
$(this).hide();
});
};
To stop the animation you can use jquery's .stop() but not sure whether it'll solve the problem or not that you are facing (didn't visualize) but you can give it a try
$('.HP-hero li').stop(1,1); // or try $('.HP-hero li').stop()
window.clearInterval(intvl);
As say2joe said that clearInterval will just stop the function from invoking next time but it won't clear the current queue (he is right) so in that case stop could be used.
About Stop.
Depending on how much work your heroTransitionNext function is doing, it may still be executing even though the interval is cleared -- in other words, clearing the interval will stop the function from being invoked -- but, any instance of the function(s) executing in memory will continue to execute until finished.
To be more clear, here's a use case (you can check this out yourself by using a profiler in Firebug or Developer Tools):
heroTransitionNext execution time is 2.1 seconds.
clearInterval is invoked 6.1 seconds after setInterval is invoked.
At 6.1 seconds, heroTransitionNext has been invoked four times. The first three executions have completed, however, the fourth will not complete until it finishes executing (at 8.1 seconds since setInterval was called). Note: In this use case, each successive invokation will execute while the last invokation's execution is still continuing (for 100 more ms) -- in other words, you'll have execution overlap from 2 to 2.1, 4 to 4.1, and 6 to 6.1 second intervals.
If the function takes longer to execute than the interval set, use a recursive function with setTimeout(). The following link will give you a good example.
Also, a good reference for explanation is https://developer.mozilla.org/en/DOM/window.setInterval.