8

I'm currently experimenting with three.js, which relies on requestAnimationFrame to perform animations.

Wouldn't the following code result in infinite recursion before the cube rotations and renderer.render function are invoked?

function render() {
    requestAnimationFrame(render);
    cube.rotation.x += 0.1;
    cube.rotation.y += 0.1;
    renderer.render(scene, camera); 
}
render();

The code works, but I'm trying to improve my overall understanding of JavaScript.

The way I see it, is that render is invoked as a callback function. But does that mean that JavaScript continues running through the code in the function before stopping to move on to the next call?

ggorlen
  • 44,755
  • 7
  • 76
  • 106
thelittlegumnut
  • 307
  • 1
  • 4
  • 11
  • 1
    My guess, is that raf works similar to setTimeout, so it places function call in event loop. – Nikolay Talanov Mar 21 '15 at 09:53
  • 3
    I actually did. I read some tutorials and also the Mozilla docs. I just struggle with these kinds of things and prefer more human readable language. Both responses helped, and they got their points, so I see no issue. Except maybe the fact there's so many people looking for an excuse to close a thread these days. Remember, it's not just helping me, but others that use Google like stackoverflow recommends. – thelittlegumnut Mar 21 '15 at 13:21

3 Answers3

8

This only requests the browser to call your callback before the next rendering loop:

You should call this method whenever you're ready to update your animation onscreen. This will request that your animation function be called before the browser performs the next repaint.

So there is no recursion here, and your function continue with the execution.

You can also cancel the request for your callback with cancelAnimationFrame.

Look here.

eladcon
  • 5,815
  • 1
  • 16
  • 19
  • and yet setting a breakpoint in the callback function shows a huge deep stack in the debugger just as though they were being called recursively – pm100 Dec 17 '21 at 04:41
3

No, the stack won't blow because requestAnimationFrame's callback is executed asynchronously. Async code is never run until the entire call stack is cleared. See this video for an explanation of the event loop.

Consider this demo to illustrate:

const fakeRAF = cb => setTimeout(cb, 1000 / 60);

(function rerender() {
  fakeRAF(rerender);
  console.log(performance.now());
})();

You can let this run all day. Of course, requestAnimationFrame is doing much more work than fakeRAF by essentially being very clever about computing when to fire cb, but this simple demo is sufficient to prove that it's not recursive, thanks to setTimeout providing an asynchronous API.

By the way, it doesn't matter whether requestAnimationFrame or fakeRAF is called at the top or the bottom of the function. All of the synchronous code in the function must run before the callback is fired so there is no issue of the next frame stepping on the previous one or something like that.

ggorlen
  • 44,755
  • 7
  • 76
  • 106
  • Ah, this is a very clever demo. So it's basically scheduling async tasks in the event loop (about ~60 tasks per second), and each call itself schedules another call in the future. So since these calls are not synchronous, the call stack doesn't overflow. – Aleksandr Hovhannisyan Feb 10 '23 at 21:50
-1

After the first render() call, RequestAnimationFrame will asynchronously take care of your render function calling it every ~60 times per second. Your function is not recursive, since Javascript will continue the code execution.

However, you should use RequestAnimationFrame only at the very end of your render function, as last command of your function. Imagine you having a big scene, with a lot of animations: requesting the next frame before completing parameters update, will probably cause a huge mess.

You can also use setTimeout to handle your animations, calling the render method with the desired frame rate, like this:

var desideredFrameRate = 60;

window.myRequestAnimationFrame = function(callback) {

    setTimeout(callback, 1000/desiredFrameRate);

}
  • 3
    Most of this answer is wrong. 1) RequestAnimationFrame calls only once 2) using RequestAnimationFrame at the start allows smoother animations (no 'mess') 3) given setTimeout's accuracy and overhead, the last piece of code is just a way to sink the animation. – GameAlchemist Mar 21 '15 at 13:20