So I'm new here, apologies if this is a basic question but I can't see it answered anywhere else.
I'm trying to write a webapp which shows an animation (using pixijs), and afterwards displays a div requesting a response. My issue is that because the animation is handled using requestAnimationFrame the animation occurs asynchronously, and so the response and animation phases occur simultaneously (the div appears instantly and obscures the animation).
Now I've looked around and the consensus seems to be to use a callback function, which only gets triggered after all the asynchronous work has been performed, allowing that serial progression.
HOWEVER, requestAnimationFrame takes the form
requestAnimationFrame(update_screen_objects),
but breaks when I try to do:
requestAnimationFrame(update_screen_objects(response_phase_callback))
as clearly requestAnimationFrame doesn't like being passed a function that itself has a callback. So my question is: what should I do to the animation loop to ensure that subsequent functions execute AFTER the animation is complete?
Thanks!
EDIT
This is an example of the code in the above form that doesn't work.
function animate(callback) {
var finished = false;
if ((new Date().getTime()) < adaptTime){
runFlicker(false);
} else if ((new Date().getTime()) < judgeTime){
hideAdaptors();
} else if ((new Date().getTime()) > judgeTime){
stage.visible = false;
finished = true;
}
renderer.render(stage);
if (!finished){
requestAnimationFrame( animate(callback) ); //Ensures it will keep looping
//requestAnimationFrame(animate); //This does work, but my issue still persists
} else {
callback(); //By the time callback() is required, you can't access it as requestAnimationFrame doesn't accept animate() with a function passed to it.
}
}
No need for a complex wrapper, just do:
requestAnimationFrame(update_screen_objects.bind(window, response_phase_callback))
This "currys" the update_screen_objects function by partially applying some arguments. The result of .bind(context, arg1) is a function that when called, only takes any arguments that aren't already bound, e.g arg2, arg3, etc.
Try this out. You basically need to generate that step (animate) function with the callback, instead of passing in the results of your call to animate, which would be undefined.
function generateAnimation(callback) {
function step() {
var finished = false;
var now = (new Date()).getTime();
if (now < adaptTime) {
runFlicker(false);
} else if (now < judgeTime) {
hideAdaptors();
} else if (now > judgeTime) {
stage.visible = false;
finished = true;
}
renderer.render(stage);
if (!finished) {
requestAnimationFrame(step);
} else {
callback();
}
}
return step;
}
// Usage:
requestAnimationFrame(generateAnimation(callback));
Related
I can't figure out why setTimeout is being called multiple times in my code.
Here's a snippet of the code with what I thought was irrelevant removed:
let dead;
setup()
{
dead = false;
}
draw()
{
if(fell == true)
{
dead = true;
}
mechanics();
}
function mechanics()
{
let triggerVar;
if(dead == true)
{
triggerVar = 1;
dead = false;
}
if(triggerVar == 1)
{
setTimeout(resetG, 1500);
triggerVar = 0;
}
}
function resetG()
{
lives -= 1;
position = 0;
}
I can't tell what I'm doing wrong because whenever the character dies and setTimeout is called, it is actually not only called after the delay but also for the exact same duration after it is triggered. So in this case it is triggered first after 1500 millis and then every frame for another 1500 millis.
I managed to find the problem, which was not with the code I posted. The problem was that the constructor code that makes the object that changes dead to true if certain conditions are met was being called every frame from the moment it triggered death until the first instance of setTimeout kicked in, which means setTimeout was called every frame for 1500 milliseconds.
Chances are that you mechanics() function is called multiple times, you may give a variable to the settimeout like:
let timeoutID= setTimeout(resetG, 1500);
And in the proper place to clear it, for example after lifecycle
clearTimeout(timeoutID);
function windowResize() {
someFunction();
console.log("test3");
}
function someFunction(){
console.log("test");
longExecutingFunctionWithAsyncReq();
console.log("test2");
}
function longExecutingFunctionWithAsyncReq() {
// some codes here
}
whenever the window is resize(zoomed out/in), this function is called.
But if the user spams the zoom, someFunction() will not have the time to finish and will then cause the error.
I'm thinking of addressing this issue by stopping the current operation and then process the new operation. Also, I've tried reading about Deferred and Promise, but I can't grasp the simplicity of the topic and I'm not sure if it really solves my problem. Plus, I've also checked on callbacks and was very doubtful that this will not solve my problem either.
If my solution is not possible though, I thought of just queuing the operations, but the downside might be, the queue might overflow if not controlled. As for this solution, I've not looked any farther to this, except reading about it.
you could use a timeout and clear it before resetting it when the resize function is called:
var myTimeout;
function windowResize() {
clearTimeout(myTimeout);
myTimeout = setTimeout(someFunction, 500);
}
this way the function will be called when the user stops resizing and 500 miliseconds have passed.
if you just need to wait for operation to finish you can set up a flag.
var working = false;
function windowResize() {
if (!working){
working = true;
someFunction();
console.log("test3");
}
}
function someFunction(){
console.log("test");
longExecutingFunctionWithAsyncReq();
console.log("test2");
}
function longExecutingFunctionWithAsyncReq() {
// some codes here
// on finish set working to False
}
var isStillWorking = false;
function windowResize() {
if(isStillWorking) {
// Do nothing.
} else {
someFunction(function(){
isStillWorking = false;
});
console.log("test3");
}
}
function someFunction(callback){
isStillWorking = true;
console.log("test");
longExecutingFunctionWithAsyncReq();
console.log("test2");
}
function longExecutingFunctionWithAsyncReq() {
// some codes here
}
To clarify more of Anton's answer I manage to implement the same thing using a flag [global] variable and a callback. I use a callback in order to flag=false since I also need to wait for the asynchronous requests inside the function to finish before resetting the flag.
I'm trying to do something like that :
I want to call the doSomething() function only when the tab has loaded class (it may take 3 or 5 seconds).
function addInfosSinistre(msg){
addInfosSinistreRecursive(msg, 0);
}
function addInfosSinistreRecursive(msg, nbCalls) {
if ($("#tab").hasClass('loaded')) {
doSomething();
}else if (nbCalls < 10) {
setTimeout(addInfosSinistreRecursive(msg, nbCalls+1),500);
}
}
This is not working, it seems like the timer doesn't work. Is there something wrong here ?
What you actually do, is to execute the function addInfosSinistreRecursive and the pass the return value to setTimeout(). What you actually want to do is to pass a function reference.
One way of doing so is to create a closure for the variables like this:
function addInfosSinistreRecursive(msg, nbCalls) {
function timeoutHandler(){
if ($("#tab").hasClass('loaded') ) {
doSomething();
}else if (nbCalls < 10) {
nbCalls += 1;
setTimeout( timeoutHandler, 500 );
}
}
// first execution
timeoutHandler();
}
The function timeoutHandler() can reference the parameters to addInfosSinistreRecursive() and has no parameters of its own. Hence you can pass a reference to it to setTimeout().
Yes, there's definitely something wrong here. You're immediately calling the function and passing undefined to setTimeout() instead of giving it a function to call. This would do what you are trying to do:
function addInfosSinistreRecursive(msg, nbCalls) {
if ($("#tab").hasClass('loaded')) {
doSomething();
}else if (nbCalls < 10) {
setTimeout(addInfosSinistreRecursive.bind(this, msg, nbCalls + 1), 500);
}
}
But the question is why are you trying to do this? It seems like a convoluted way to do whatever it is you are trying to accomplish. You should probably be using an event somewhere when the tab is changed rather than recursively looping and waiting for the tab to change.
I would like to animate an html page with something like this:
function showElements(a) {
for (i=1; i<=a; i++) {
var img = document.getElementById(getImageId(i));
img.style.visibility = 'visible';
pause(500);
}
}
function pause(ms) {
ms += new Date().getTime();
while (new Date() < ms){}
}
Unfortunately, the page only renders once javascript completes.
If I add
window.location.reload();
after each pause(500); invocation, this seems to force my javascript to exit. (At least, I do not reach the next line of code in my javascript.)
If I insert
var answer=prompt("hello");
after each pause(500), this does exactly what I want (i.e. update of the page) except for the fact that I don't want an annoying prompt because I don't actually need any user input.
So... is there something I can invoke after my pause that forces a refresh of the page, does not request any input from the user, and allows my script to continue?
While the javascript thread is running, the rendering thread will not update the page. You need to use setTimeout.
Rather than creating a second function, or exposing i to external code, you can implement this using an inner function with a closure on a and i:
function showElements(a) {
var i = 1;
function showNext() {
var img = document.getElementById(getImageId(i));
img.style.visibility = 'visible';
i++;
if(i <= a) setTimeout(showNext, 500);
}
showNext();
}
If I add window.location.reload(); after each pause(500) invocation, this seems to force my javascript to exit
window.reload() makes the browser discard the current page and reload it from the server, hence your javascript stopping.
If I insert var answer=prompt("hello"); after each pause(500), this does exactly what I want.
prompt, alert, and confirm are pretty much the only things that can actually pause the javascript thread. In some browsers, even these still block the UI thread.
Your pause() function sleeps on the UI thread and freezes the browser.
This is your problem.
Instead, you need to call setTimeout to call a function later.
Javascript is inherently event-driven/non-blocking (this is one of the great things about javascript/Node.js). Trying to circumvent a built in feature is never a good idea. In order to do what you want, you need to schedule your events. One way to do this is to use setTimeout and simple recursion.
function showElements(a) {
showElement(1,a);
}
function showElement(i, max) {
var img = document.getElementById(getImageId(i));
img.style.visibility = 'visible';
if (i < max) {
setTimeout(function() { showElement(i+1, max) }, 500);
}
}
var i = 1;
function showElements(a) {
var img = document.getElementById(getImageId(i));
img.style.visibility = 'visible';
if (i < a) {
setTimeout(function() { showElements(a) }, 500);
}
i++;
}
showElements(5);
function showElements(a,t) {
for (var i=1; i<=a; i++) {
(function(a,b){setTimeout(function(){
document.getElementById(getImageId(a)).style.visibility = 'visible'},a*b);}
)(i,t)
}
}
The t-argument is the delay, e.g. 500
Demo: http://jsfiddle.net/doktormolle/nLrps/
I have a setInterval calling a loop which displays an animation.
When I clearInterval in response to a user input, there are possibly one or more loop callbacks in queue. If I put a function call directly after the clearInterval statement, the function call finishes first (printing something to screen), then a queued loop callback executes, erasing what I wanted to print.
See the code below.
function loop() {
// print something to screen
}
var timer = setInterval(loop(), 30);
canvas.onkeypress = function (event) {
clearInterval(timer);
// print something else to screen
}
What's the best way to handle this? Put a delay on the // print something else to screen? Doing the new printing within the loop?
Edit: Thanks for the answers. For future reference, my problem was that the event that triggered the extra printing was buried within the loop, so once this executed, control was handed back to the unfinished loop, which then overwrote it. Cheers.
You could also use a flag so as to ignore any queued functions:
var should;
function loop() {
if(!should) return; // ignore this loop iteration if said so
// print something to screen
}
should = true;
var timer = setInterval(loop, 30); // I guess you meant 'loop' without '()'
canvas.onkeypress = function (event) {
should = false; // announce that loop really should stop
clearInterval(timer);
// print something else to screen
}
First of all, you probably meant:
var timer = setInterval(loop, 30);
Secondly, are you sure calling clearInterval does not clean the queue of pending loop() calls? If this is the case, you can easily disable these calls by using some sort of guard:
var done = false;
function loop() {
if(!done) {
// print something to screen
}
}
var timer = setInterval(loop(), 30);
canvas.onkeypress = function (event) {
clearInterval(timer);
done = true;
// print something else to screen
}