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?
Related
Background
Suppose I have a metaphorical patient whose heart beats once per second, and every second, I check whether his last heartbeat was more than five seconds late (and, if so, declare him to be in danger):
let lastHeartbeat = Date.now();
// Heartbeater
setInterval(() => lastHeartbeat = Date.now(), 1000);
// Health-checker
setInterval(
() => {
if((Date.now() - lastHeartbeat) > 5000){
alert("Patient has flatlined");
}
},
1000
);
Issue
If I use a debugger to pause the execution of this script at some point, I have a problem: if I remain on a breakpoint for more than five seconds, then, once script execution resumes, my health-checker function is certain to declare that the patient has flatlined.
Desired behaviour
Instead, I'd like to factor in the time spent in the debugger.
i.e., if I spend twenty seconds sitting on a breakpoint just after an initial heartbeat occurring, and the patient's heart beats again within just one second of releasing that breakpoint, then that patient should not be declared as flatlining.
Is there any way to subtract the time spent in the debugger from the health-check condition? e.g.:
if((Date.now() - lastHeartbeat - lastTimeSpentInDebugger) > 5000)
Note: I'm specifically running the JS in Node.js, rather than in a browser.
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 write extension for Chrome. And I need run delayed tasks when background page inactive. Cause setTimeout not working in background tabs, I try emulate setTimeout with setInterval, like code below (located in content script):
window.timings = [];
function set_timeout(func, time){
var now = new Date() / 1;
window.timings.push({
func: func,
time: time + now
});
}
function tick(){
var now = new Date() / 1;
window.timings = window.timings.filter(function(delay_obj){
if (now > delay_obj.time){
delay_obj.func.call();
return false;
} else {
return true;
}
});
}
$(function() {
setInterval(tick, 1000);
// some code
});
And it don't work when set_interval call in delay function:
set_timeout(function(){
console.log('func1');
}, 2000);
set_timeout(function(){
console.log('func2');
set_timeout(function(){
console.log('func3');
}, 3000);
}, 3000);
Output:
func1
func2
Why func3 not displayed?
You're apparently using an event page declared with "persistent": false in manifest.json, it is unloaded after 15 seconds of inactivity. The linked documentation says to use chrome.alarms API.
For a delay less than 15 seconds since the last chrome event:
Use setTimeout or setInterval.
For a delay of 15-60 seconds since the last chrome event:
Don't use the event page, switch to "persistent": true in manifest.json.
For a delay of 60 seconds or more:
manifest.json:
"permissions": ["alarms"],
background script:
chrome.alarms.create("MyInterval1", {when: Date.now() + 1 * 60e3});
chrome.alarms.onAlarm.addListener(function(alarm) {
if (alarm.name == "MyInterval1") {
console.log("Yay!");
chrome.alarms.create("MyInterval1", {when: Date.now() + 1 * 60e3});
}
});
Also note:
Other asynchronous HTML5 APIs like notifications and geolocation will not complete if the event page shuts down. Instead, use equivalent extension APIs, like notifications.
If your extension uses, extension.getBackgroundPage, switch to runtime.getBackgroundPage instead. The newer method is asynchronous so that it can start the event page if necessary before returning it.
Note that in a published extension the interval between the next alarm and the last fired alarm is at least 1 minute even if you specify a smaller value like 15 seconds (15*1000).
Source: https://developer.chrome.com/docs/extensions/reference/alarms/#method-create
In order to reduce the load on the user's machine, Chrome limits
alarms to at most once every 1 minute but may delay them an arbitrary
amount more. That is, setting delayInMinutes or periodInMinutes to
less than 1 will not be honored and will cause a warning. when can
be set to less than 1 minute after "now" without warning but won't
actually cause the alarm to fire for at least 1 minute.
To help you debug your app or extension, when you've loaded it
unpacked, there's no limit to how often the alarm can fire.
We have created an application using Dojo with an clock on the UI. But sometimes the application UI just hung-up there and the clock just stopped. Guessing the JS engine just stopped because the clock is driven by javascript code.
Not sure following code causes memory leak and then causes the hang-up issue. We are using recursively setTimeout invoke to implement the clock.
dojo.declare("xxx.xxx.HomepageHeader", [dijit._Widget, dijit._Templated],
{
widgetsInTemplate: true,
_time :'',
dateUtil: null,
// ....
// ....
prefix :function (value, p)
{
return (value < 10) ? p + value : value;
},
updateTime :function ()
{
var d = new Date();
var _this = this;
var t = [_this.prefix(d.getHours(), '0'), _this.prefix(d.getMinutes(), '0'), _this.prefix(d.getSeconds(), '0')].join(':');
_this._time.innerHTML = t;
_this.dateInfo.innerHTML = this.dateUtil.format(d, "yyyy/MM/dd") + " |  " + this.dateUtil.format(d, "EEE");
window.setTimeout( function(){_this.updateTime();}, 100);
}
// ....
// ....
}
Noticed that within the class, the method updateTime used window.setTimeout to recursively invoke itself to update time text on the UI.
Is there any memory leak issue here? If the answer is no, is there any possible issue caused the hang up issue?
Thanks!
This isn't really recursion because setTimeout() schedules the next call to updateTime() for some time in the future and then the current updateTime() actually completes. So, there is no build-up of the stack frame and thus it's not really recursion. Also, I don't see any reason for a memory leak here.
The scheme you have should be OK and it used often, but you may want to do it less often than every 100ms just to leave more CPU cycles for other things going on the browser.
If you see the clock stop, it is because the JS engine is stuck or looping, but it's probably stuck in code somewhere else other than the actual clock code.
I am trying to make a simple hidden object game using javascript. When the user finds and clicks an image, I want 3 things to happen in the following order; a sound plays, the image size increases, and the image goes invisible. The problem I am running into is getting the 3 events to happen sequentially, not concurrent. Right now, seems that all three events happen all at the same time.
I've tried using setTimeout(), and while that does create a delay, it still runs all functions at the same time, even if each function is nested in setTimeout.
Example: (all this does is waits 1.5 sec then plays the sound and makes the image invisible):
function FindIt(image, id){
var t = setTimeout('sound()',10);
var b = setTimeout('bigger(' + image + ')',30);
var h = setTimeout('hide(' + image + ')',1500);
}
Below are the functions I am currently using and the actual results are: click the image, nothing happens for 2 seconds, then the sound plays and the image goes invisible.
function FindIt(image, id){
sound();
bigger(image);
hide(image);
}
function sound(){
document.getElementById("sound_element").innerHTML= "<embed src='chime.wav' hidden=true autostart=true loop=false>";
}
function bigger(image){
var img = document.getElementById(image);
img.style.width = 112;
img.style.height = 112;
}
function hide(id){
var ms = 2000;
ms += new Date().getTime();
while (new Date() < ms){} //Create a 2 second delay
var img = document.getElementById(id);
img.style.visibility='hidden';
}
Any guidance would be greatly appreciated!
To trigger things sequentially, you need to execute the second item some amount of time after the first one completes, execute the third item some amount of time after the second one completes, etc...
Only your sound() function actually takes some time, so I'd suggest the following:
function FindIt(image, id){
sound();
// set timer to start next action a certain time after the sound starts
setTimeout(function() {
bigger(image);
// set timer to start next action a certain time after making the image bigger
setTimeout (function() {
hide(image);
}, 1000); // set this time for how long you want to wait after bigger, before hide
}, 1000); // set the time here for how long you want to wait after starting the sound before making it bigger
}
FYI, the animation capabilities in libraries like jQuery or YUI make this sort of thing a lot easier.
Also, please don't use this kind of construct in your JS:
while (new Date() < ms){}
That locks up the browser for that delay and is very unfriendly to the viewer. Use setTimeout to create a delay.
For reference, using the animation libraries in jQuery, the jQuery code to handle a click on the object and then animate it over a 2 second period to a larger size, delay for 1 second, then slideup to disappear is as follows:
$("#rect").click(function() {
$(this).animate({height: 200, width: 400}, 2000).delay(1000).slideUp();
});
jQuery manages an animation queue and handles setting all the timers and doing all the sequencing and animation for you. It's a lot, lot easier to program and gives a very nice result.
You can see it work and play with it here: http://jsfiddle.net/kC4Mz/.
why don't use "event" approach. like onTaskDone();
function task1(arg, onTask1Done){
console.log(arg);
if(onTask1Done)onTask1Done();
}
task1("working", function(){console.log("task2");});
The Frame.js library is designed to elegantly handle situations like this:
function FindIt(image, id){
Frame(10, function(next) { sound(); next(); });
Frame(30, function(next) { bigger(image); next(); });
Frame(1500, function(next) { hide(image); next(); });
Frame.start();
}
Frame.js offers many advantages over using standard timeouts, especially if you are doing a lot of this kind of thing, which for a game, you likely are.
https://github.com/bishopZ/Frame.js