animationFrame based throttling and events

I've created a tiny utility function that takes throttles a function such that it fires at most once per animationFrame. The first time the method is invoked, it will be executed immediately. Any subsequent invocations within that particular frame will be blocked. All blocked invocations will be dropped except for the very last one, which will be deferred to the next frame. The invocation that gets deferred to the next frame will count as the first and only invocation allowed for that particular frame.

For example:
3 calls within frame 1, 2 calls within frame 2:
- 1st call executes once, immediately.
- 2nd call gets dropped.
- 3rd call excutes once, during the 2nd frame.
- 4th call gets dropped.
- 5th call executes once, during 3rd frame.

And of cource, the code:
    /**
     * Returns a function, that, when invoked, will only be triggered at most
     * once per animation frame.
     *
     * If called multiple times within a single animation frame, it will be
     * triggered once in the current frame, and once on the next frame.
     */
    var oncePerAnimationFrame = function(fn) {
        var executed = false,
            queued = false;

        return function() {
            // Current frame has not executed the function yet:
            // Execute immediately and mark frame as executed.
            if (!executed) {
                fn.apply(this, arguments);
                executed = true;
            }
            // Current frame has already executed the function, enqueue it for
            // the next frame.
            else {
                queued = true;
            }

            // Request a new frame
            enyo.requestAnimationFrame(function() {
                // New frame, not yet executed.
                executed = false;

                // A function call has been queued.
                // Execute it, clear queue, mark frame as executed.
                if (queued) {
                    fn.apply(this, arguments);
                    queued = false;
                    executed = true;
                }
            }, this.node);
        };
    };
I'm using this method to throttle event handlers of pinch & zoom gestures, but I thought it might come in handy in other situations as well. Feel free to use it as you see fit.

@ the enyo team. I think it could also be interesting to optionally throttle specific bindings and computed properties by animationFrame? Many bindings result in DOM changes and particularly computed values with multiple parameters can get re-calculated very often.

Comments

  • Yeah I saw Aaron made some cool commits with a BackgroundTaskManager. I also saw some Flux components added. Looks like some very interesting developments are coming to enyo.

    Will the new BTM be combined with bindings and computed properties? Or is it better to explore combining my current utility with bindings?
  • edited April 2015
    By the way, can we expect some blog posts to go along with these new big-impact features?
  • edited April 2015
    
    /**
     * Returns a function, that, when invoked, will only be triggered at most
     * once per animation frame.
     *
     * If called multiple times within a single animation frame, it will be
     * triggered once in the current frame, and once on the next frame.
     */
    var oncePerAnimationFrame = function(fn) {
        var executed = false,
            queued = false,
            args = [],
            ctx = this;
    
        return function() {
            // Current frame has not executed the function yet:
            // Execute immediately and mark frame as executed.
            if (!executed) {
                fn.apply(this, arguments);
                executed = true;
            }
            // Current frame has already executed the function, enqueue it for
            // the next frame.
            else {
                queued = true;
                args = arguments;
                ctx = this;
            }
    
            // Request a new frame
            enyo.requestAnimationFrame(function() {
                // New frame, not yet executed.
                executed = false;
    
                // A function call has been queued.
                // Execute it, clear queue, mark frame as executed.
                if (queued) {
                    fn.apply(ctx, args);
                    args = [];
                    ctx = this;
                    queued = false;
                    executed = true;
                }
            }, this.node);
        };
    };
    
    A better version, one that actually remembers context and arguments when deferring a function call.
Sign In or Register to comment.