Promises, async functions, and requestAnimationFrame, together.

/#!/JoePea joe at trusktr.io
Sat Apr 23 22:18:35 UTC 2016


Alright, I did an experiment, and I'm really surprised at the results!
Apparently, the logic (what would be drawSomething() in my previous
example) is fired within the frame!!

So, let me show you my original method for starting an animation loop.
I'm working on a 3D project at http://infamous.io. The Scene class
(https://github.com/infamous/infamous/blob/master/src/motor/Scene.js)
has a method for starting an animation loop the standard way:

```js
    async _startAnimationLoopWhenMounted() {
        this._animationLoopStarted = true

        if (!this._mounted) await this.mountPromise

        // So now we can render after the scene is mounted.
        const loop = timestamp => {
            this._inFrame = true

            this._runRenderTasks(timestamp)
            this._renderNodes(timestamp)

            // If any tasks are left to run, continue the animation loop.
            if (this._allRenderTasks.length)
                this._rAF = requestAnimationFrame(loop)
            else {
                this._rAF = null
                this._animationLoopStarted = false
            }

            this._inFrame = false
        }

        this._rAF = requestAnimationFrame(loop)
    }
```

Here's what the Chrome timeline shows for the logic that is fired
inside the loop:
https://cloud.githubusercontent.com/assets/297678/14764236/8eb72d4a-0965-11e6-9bb9-5db02cc23520.png

Now, I went ahead and modified my Scene class so the method now looks like this:

```js
function animationFrame() {
    let resolve = null
    const promise = new Promise(r => resolve = r)
    window.requestAnimationFrame(resolve)
    return promise
}

// ...

    async _startAnimationLoopWhenMounted() {
        this._animationLoopStarted = true

        if (!this._mounted) await this.mountPromise

        this._rAF = true
        let timestamp = null
        while (this._rAF) {
            timestamp = await animationFrame()
            this._inFrame = true

            this._runRenderTasks(timestamp)
            this._renderNodes(timestamp)

            // If any tasks are left to run, continue the animation loop.
            if (!this._allRenderTasks.length) {
                this._rAF = null
                this._animationLoopStarted = false
            }

            this._inFrame = false
        }
    }
```

And the timeline results are surprising! As you can see in the
following screenshot, all of the logic happens within the frame
(though you can see there's extra overhead from what I assume are the
extra function calls due to the fact that I'm using Facebook
Regenerator for the async functions):
https://cloud.githubusercontent.com/assets/297678/14764237/8eb71ce2-0965-11e6-942a-3c556c48b9a0.png

Near the bottom right of the screen shot, you can see the tooltip as
I'm hovering on the call to `animationFrame` which returns the promise
that I am awaiting in the loop.

Although this behavior seems to be exactly what I was hoping for, it
seems like there is something wrong. Could there be a bug in
regenerator that is failing to defer my loop code to a following tick?
Or is my code deferred to a following tick that somehow the animation
frame knows to execute within the same tick? Maybe there's something
I'm missing about the Promise API that allows for .then() of a promise
(which I assume is what Regenerator is using) to be executed in the
same tick? What's going on here?

I was expecting to see the code of my loop execute outside of the
"Animation Frame Fired" section.

On Sat, Apr 23, 2016 at 6:28 AM, Boris Zbarsky <bzbarsky at mit.edu> wrote:
> On 4/23/16 4:09 AM, Salvador de la Puente González wrote:
>>
>> AFAIK, that should execute `drawSomething()` once per frame. Given a
>> frame is each time the animatinFrame() promise resolves.
>
>
> What's not obvious to me is whether it will execute it before the actual
> paint for the frame (i.e. before or right after the loop that's notifying
> the animation frame callbacks completes) or whether it will do
> drawSomething() after the paint...
>
> -Boris
>
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss


More information about the es-discuss mailing list