Flow Control (v3)

Revision 3 of this benchmark created by Alan Gutierrez on


Description

A popular strategy for flow control in JavaScript is to use a list of closures and call one closure with the results of the previous closure, as in the delightful little Step library by Tim Caswell.

Step(
  function readSelf() {
    fs.readFile(__filename, this);
  },
  function capitalize(err, text) {
    if (err) throw err;
    return text.toUpperCase();
  },
  function showIt(err, newText) {
    if (err) throw err;
    console.log(newText);
  }
);

I was wondering if how the creation of a handful of closures at function invocation would compare to the use of an object, where the steps would be member functions in the object, and state would be preserved in this.

These tests are not asynchronous. They simply wrap a two step operation in helper functions. The first step pops an element from an array. The second step sums the remaining array elements.

I encountered an interesting case with Chrome 19 where assigning one of the closures functions to an object property of any object in the enclosing function causes significant drop in performance, so there are additional tests to illustrate that case.

Setup

var y = {};
    var test = (function() {
      var test = {};
      test.closure = function closure(array) {
        var popper, summer;
        popper = function() {
          array.pop();
          return summer();
        }
        summer = function() {
          var i, I, sum = 0;
          for (i = 0, i < array.length; i < I; i++) sum += array[i];
          return sum;
        }
        return popper();
      }
      test.objectAssignedUncalled = function objectAssignedUncalled(array) {
        var x = {};
        x.popper = function() {
          array.pop()
        };
        function helper() {
          var i, I, sum = 0;
          for (i = 0, I = array.length; i < I; i++) sum += array[i];
          return i;
        }
        return helper();
      }
      test.objectAssigned = function objectAssigned(array) {
        var z = {};
        z.popper = function () {
          array.pop();
          return helper();
        };
        return z.popper();
        function helper() {
          var i, I, sum = 0;
          for (i = 0, I = array.length; i < I; i++) sum += array[i];
          return i;
        }
      }
      test.objectAssignedViaVar = function objectAssignedViaVar(array) {
        var z = {}, popper = function () {
          array.pop();
          return summer();
        };
        z.popper = popper;
        return z.popper();
        function summer() {
          var i, I, sum = 0;
          for (i = 0, I = array.length; i < I; i++) sum += array[i];
          return i;
        }
      }
      test.hoisted = function hoisted(array) {
        function popper() {
          array.pop();
          return summer();
        }
    
        function summer() {
          var i, I, sum = 0;
          for (i = 0, i < array.length; i < I; i++) sum += array[i];
          return sum;
        }
        return popper();
      }
    
      function Proto() {}
      Proto.prototype.popper = function(array) {
        this.array = array;
        array.pop();
        return this.summer();
      }
      Proto.prototype.summer = function() {
        var array = this.array,
            sum = 0,
            i, I;
        for (i = 0, I = array.length; i < I; i++) sum += array[i];
        return sum;
      }
      test.proto = function proto(array) {
        return (new Proto()).popper(array);
      }
      Proto.cache = [];
      test.cachedProto = function cachedProto(array) {
        var proto = Proto.cache.length ? Proto.cache.pop() : (new Proto()),
            sum = proto.popper(array);
        Proto.cache.push(proto);
        return sum;
      }
      var popper, summer;
      test.outerAssigned = function outerAssigned(array) {
        popper = function () {
          array.pop();
          return summer();
        }
        summer = function () {
          var i, I, sum = 0;
          for (i = 0, i < array.length; i < I; i++) sum += array[i];
          return sum;
        }
        return popper();
      }
      return test;
    })();

Test runner

Ready to run.

Testing in
TestOps/sec
Closures.
test.closure([1, 2, 3, 4]);
ready
Hoisted closures.
test.hoisted([1, 2, 3, 4]);
ready
Prototype.
test.proto([1, 2, 3, 4]);
ready
Cached prototype.
test.cachedProto([1, 2, 3, 4]);
ready
Closure assigned to object property.
test.objectAssigned([1, 2, 3, 4]);
ready
Closure assigned to object property, not called.
test.objectAssignedUncalled([1, 2, 3]);
ready
Closures assigned to outer variables.
test.outerAssigned([1, 2, 3, 4]);
ready
Closure assigned to object property via variable.
test.objectAssignedViaVar([1, 2, 3, 4]);
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.

  • Revision 3: published by Alan Gutierrez on