Loktopus
Loktopus

Reputation: 462

Alternatives to deep nesting functions javascript

I've been trying to organize code in a javascript project and have ended up with a bunch of nested functions. Supposedly that's bad practice, and I know it affects performance, but I'm having trouble coming up with an alternative. Here's an example of what I'm trying to do:

Code before nesting:

function Level1(dep1, dep2, dep3, dep4){

    var tempResult1 = dep1 + dep2;
    var tempResult2 = tempResult1/2;
    var tempResult3 = dep3 + dep4;
    var mainResult = tempResult2 + tempResult3;

    return mainResult;

}

What it looks like after I try to separate responsibilities into a hierarchy:

function Level1(dep1, dep2, dep3, dep4){

    var tempResult2 = getTempResult2(dep1, dep2);
    var tempResult3 = getTempResult3(dep3, dep4);
    var mainResult = tempResult2 + tempResult3;
    return mainResult;

    function getTempResult2(dep1, dep2){

        var tempResult1 = getTempResult1(dep1, dep2);
        return tempResult1/2;

        function getTempResult1(dep1, dep2){

            return dep1 + dep2;

        }
    }

    function getTempResult3(dep3, dep4){

        return dep3 + dep4;
    }        

}

Obviously for the operations used here, functions are a bit excessive, but it does help make the ones in my project a lot more manageable. I'm unfamiliar with any other way to do this, as this is my first javascript project. The suggestions I found on here only dealt with 1 level of nested functions, not 2, and I didn't see any good examples of how to implement nested scoping. If someone could give me an example of a way to accomplish the organization I'm looking for here, I'd be very greatful. Thanks.

Upvotes: 1

Views: 1475

Answers (7)

user1115652
user1115652

Reputation:

This is a design question. Since a good and elegant design depends always on quite a number of aspects of your problem domain, it is impossible from the simplified code in your question to really estimate what the best solution would be. I'll try to show you some options which in each case address ways to do data hiding and avoid nested functions or functions that are created multiple times.

Since you ask for data hiding and keeping getTempResult1 hidden from anything other than getTempResult2, I will assume that each of these functions are reasonably complex and might be written by different people, and you want to keep the internals hidden. That warrants creating a different class for each of these rather than just a function.

I will substitute your example code with a more tangible example and use a proper OOP approach to solve the problem. Let's say you are building an e-commerce application. Level1 will be an invoice class, and dep1-4 might be things like: purchase price, profit rate, discount rate, tax rate. We will make a very simple application which will calculate: purchase price - discount + profit + taxes = total price

I hope this ressembles your problem faithfully enough so you can appreciate some of the OOP techniques used (it's still way overkill in structure for the calculations done, but it's a lesson in OOP and allows for a great deal in scalability should the problem domain get more complex in the future, say you go international and must calculate taxes for different countries etc).

I will use the OoJs library to be able to do proper OOP in JavaScript. See the code below working on jsfiddle.

The idea of an ecommerce app is inspired from the book "Dessign patterns explained" by Shalloway and Trott

In conclusion you will find that this solution:

  • hides implementation details
  • there are no nested functions
  • every function is created only once
  • is scalable and maintainable and flexible in case of changing requirements

So the code using our classes will look as follows:

// Create your namespace to avoid polluting global
//
var eComm = eComm || {}


// this will be some factory code which will return the needed objects, it won't actually use them.
// Normally this should be a class living in our eComm namespace
//
function factory( db, clientID )
{
   // we would assume here that the hardcoded rates would be found in the database using the client id.
   //
   var discount = new eComm.Discount( 5            ) // in %
   var profit   = new eComm.Profit  ( 20, discount ) // in %
   var taxRate  = new eComm.TaxRate ( 5 , profit   ) // in %


   // note that I use a simple aggragation approach, because I don't know the
   // actual complexity of your problem domain. It makes this very simple ecommerce
   // code not entirely ideal. If we would just perform a chain of operations on
   // a number, other design patterns would be more suited, like a decorator.
   // It is not appropriate to just pass taxRate to Invoice, because it is no different
   // than profit or discount, it just comes later in a chain of calculations.
   // I have taken this approach to show that it is possible to hide steps of the
   // implementation down a hierarchy.
   //
   return new eComm.Invoice( taxRate )
}


// now when we will actually use it, it looks like this
// wrapped it in a function call because on global scope
// we would have to put this entirely at the bottom
// if you put all your code in classes you don't have this
// problem. They can occur in any order
//
function usage()
{
   var invoice = factory( "database", 1654 /* the client id */ )

   invoice.addPurchase( 1574 ) // price in some currency
   invoice.addPurchase( 1200 ) // a second purchase


   // in reality you would probably also pass an object representing an output
   // device to Invoice (a printer, or a pdf generator, ...)
   //
   console.log( invoice.total() )
}

The actual classes. It looks long, but that's because the less they do, the bigger the overhead (relatively speaking). I ommit more and more code as we go down for brevity as the classes all look very much alike.

;( function class_Invoice( namespace )
{
   'use strict'; // recommended

   if( namespace[ "Invoice" ] ) return    // protect against double inclusions

       namespace.Invoice = Invoice
   var Static            = OoJs.setupClass( namespace, "Invoice" )



   // constructor
   //
   function Invoice( taxRate )
   {
      // should do validation as javascript is loosely typed
      //
      if( "TaxRate" !== OoJs.typeOf( taxRate ) )

         ;// throw an error


      // Data members
      //
      this.taxRate    = taxRate
      this.totalPrice = 0


      this.Protected( "taxRate", "totalPrice" ) // if you want them available to subclasses


      var iFace = this.Public( total, addPurchase ) // make these methods public

      return iFace
   }


   // all your method definitions go here
   //

   function addPurchase( price )
   {
      this.totalPrice += this.taxRate.calculate( price )
   }


   function total()
   {
      return this.totalPrice
   }

})( eComm )



;( function class_TaxRate( namespace )
{
       namespace.TaxRate = TaxRate
   var Static            = OoJs.setupClass( namespace, "TaxRate" )


   // constructor
   //
   function TaxRate( rate, profit )
   {
      // do your validation on profit and rate as above

      this.rate   = rate
      this.profit = profit

      this.Protected( "profit" ) // if you want

      return this.Public( calculate )
   }


   function calculate( price )
   {
      return this.profit.calculate( price ) * ( 1 + this.rate / 100 )
   }

})( eComm )



;( function class_Profit( namespace )
{
       namespace.Profit = Profit
   var Static           = OoJs.setupClass( namespace, "Profit" )


   // constructor
   //
   function Profit( rate, discount )
   {
      this.rate     = rate
      this.discount = discount

      return this.Public( calculate )
   }


   function calculate( price )
   {
      return this.discount.calculate( price ) * ( 1 + this.rate / 100 )
   }

})( eComm )



;( function class_Discount( namespace )
{
       namespace.Discount = Discount
   var Static             = OoJs.setupClass( namespace, "Discount" )


   // constructor
   //
   function Discount( rate )
   {
      this.rate = rate

      return this.Public( calculate )
   }


   function calculate( price )
   {
      return price - price * this.rate / 100
   }

})( eComm )


usage()

Upvotes: 1

HMR
HMR

Reputation: 39270

It's a good idea to have a function implement as less as possible for optimal re use. For example:

function doChores(){
  //actually wash the dishes
  //actually walk the dog.
}

Now let's say it's raining and I only want to wash the dishes, since washing the dishes is implemented in doChores I can't call it without walking the dog. Here is how it should be done:

function doChores(){
  walkTheDog();
  washTheDishes();
}

The function walkTheDog implements walking the dog and washTheDishes implements washing the dishes so they can be called sperately.

The problem you're facing is when you pass variables to a chain of functions. I usually pass one argument to a function and that argument contains an object with the needed parameters. Every function can read or mutate members of the passed object that they are concerned with. If at a later time you need to add more arguments then you don't need to change the signature of your funciton (for example function(arg1, arg2, newArg)) you'll always have function(args)

More info about the parameter passing can be found here: https://stackoverflow.com/a/16063711/1641941 under Passing (constructor) arguments

Upvotes: 0

xCNPx
xCNPx

Reputation: 605

I know this is already answered but I thought I would leave you with some additional resources to help you on this adventure. I think this is a perfect time for you to look deeper into javascript design patterns.

Learning JavaScript Design Patterns by Addy Osmani is a fantastic read / resource to learn about multiple patterns for creating javascript applications, creating reusable code, closures etc. Anyone who is having this internal disscussion on how to better organize my nested functions / scope, etc should read it.

Here is an example snippet from his article regarding The Factory Pattern

// Types.js - Constructors used behind the scenes

// A constructor for defining new cars
function Car( options ) {

 // some defaults
 this.doors = options.doors || 4;
 this.state = options.state || "brand new";
 this.color = options.color || "silver";

}

// A constructor for defining new trucks
function Truck( options){

  this.state = options.state || "used";
  this.wheelSize = options.wheelSize || "large";
  this.color = options.color || "blue";
}


// FactoryExample.js

// Define a skeleton vehicle factory
function VehicleFactory() {}

// Define the prototypes and utilities for this factory

// Our default vehicleClass is Car
VehicleFactory.prototype.vehicleClass = Car;

// Our Factory method for creating new Vehicle instances
VehicleFactory.prototype.createVehicle = function ( options ) {

switch(options.vehicleType){
  case "car":
  this.vehicleClass = Car;
  break;
  case "truck":
    this.vehicleClass = Truck;
  break;
  //defaults to VehicleFactory.prototype.vehicleClass (Car)
 }

return new this.vehicleClass( options );

};


// Create an instance of our factory that makes cars
var carFactory = new VehicleFactory();
var car = carFactory.createVehicle( {
            vehicleType: "car",
            color: "yellow",
            doors: 6 } );

// Test to confirm our car was created using the vehicleClass/prototype Car

// Outputs: true
console.log( car instanceof Car );

// Outputs: Car object of color "yellow", doors: 6 in a "brand new" state
console.log( car );

Hope this article helps you and others looking for similar answers.

Upvotes: 0

Bergi
Bergi

Reputation: 664599

have ended up with a bunch of nested functions

You can simply unnest them, since they're not used as closures (you pass everything necessary as arguments). You will even get a little performance advantage by not creating a local function object every time the outer function is called (which is well optimized though and probably negligible).

I just read a bunch about how nested functions were bad practice

Notice that sometimes nested functions are necessary, when you want to create a closure.

I don't like the fact that getTempResult1, 2, and 3 are accessible outside of Level1, or that getTempResult1 is accessible outside of getTempResult2.

You could use an IEFE to create an extra scope from which only Level1 is exported:

var Level1 = (function() {
    function Level1(dep1, dep2, dep3, dep4) {
        var tempResult2 = getTempResult2(dep1, dep2);
        var tempResult3 = getTempResult3(dep3, dep4);
        var mainResult = tempResult2 + tempResult3;
        return mainResult;
    }

    var getTemptResult2 = (function() {
        function getTempResult2(dep1, dep2) {
            var tempResult1 = getTempResult1(dep1, dep2);
            return tempResult1/2;
        }

        function getTempResult1(dep1, dep2) {
            return dep1 + dep2;
        }

        return getTempResult2;
    })();

    function getTempResult3(dep3, dep4) {
        return dep3 + dep4;
    }        

    return Level1;
}());

Upvotes: 2

Stephan Kulla
Stephan Kulla

Reputation: 5067

Maybe this is what you want:

function Level1(dep1, dep2, dep3, dep4){
    var tempResult2 = Level1.getTempResult2(dep1, dep2);
    var tempResult3 = Level1.getTempResult3(dep3, dep4);
    var mainResult = tempResult2 + tempResult3;

    return mainResult;
}

Level1.getTempResult2 = function (dep1, dep2) {
    var tempResult1 = Level1.getTempResult2.getTempResult1(dep1, dep2);
    return tempResult1/2;   
}

Level1.getTempResult2.getTempResult1 = function (dep1, dep2){
    return dep1 + dep2;
}

Level1.getTempResult3 = function (dep3, dep4){
    return dep3 + dep4;
}

Currently I tried

function a(val1, val2) { return a.foo(val1, val2) }
a.foo = function (x,y) { return x + y }

in my browser. The command a(1,2) prints 3 as aspected. Other example:

function a() { return a.foo(1,2) }
a.foo = function (x,y) { return a.foo.bar(x,y) }
a.foo.bar = function (x,y) { return x+y }
a(1,2) // -> 3

Upvotes: 2

cybersam
cybersam

Reputation: 66999

Try looking at the step module, which is intended for node.js. I use it all the time.

However, you might be to use the step.js script even outside of the node.js environment (note: I have not tested this). At the very least, it shows how you can flatten any number of levels of nesting.

Upvotes: 0

Jeremy Danyow
Jeremy Danyow

Reputation: 26406

you don't need to worry about the call stack getting a few levels deep.

this is an example of premature optimization

Upvotes: 0

Related Questions