Writing Reusable Javascript

It’s very easy to lose sight of the big picture when writing Javascript. Your attention is spent entirely on the little details of the current bit of code you’re writing, not why you started writing them in the first place. This isn’t necessarily a bad thing. What’s problematic is doing it repeatedly for the same type of problem.

I’ll walk you through how I wrote a reusable function, how it evolved and my thought process the entire journey, so you can recognize the patterns and know when it’s time to make something reusable.


I’m working on a webapp that collects information from different members of an organization and displays it on their public website. This means much of the work is data storage and processing. Majority of my code will be looping over data structures and doing something in the loop.

for (var index = 0; index < array.length; index++){
 
  var item = array[index];

  // do something with item
}

No big deal writing it the first or the next time. By the fourth or fifth time though, not quite as fun. All that code is trying to say is take every item in this array and do something with it. Decided to make it a function since even the most verbose function name is a lot shorter and more intuitively understood than the first two lines of that snippet.

function array_each(array, process_item){
  
  for (var index = 0; index < array.length; index++){
 
    var item = array[index];

    process_item( item );
  }
}

Every time I need to loop over an array, it now looks so much neater.

// greet users currently online

var users_online = ["Batman", "Superman", "Wonder Woman", "Flash", "Cyborg"];

array_each(users_online, function(user){
  
  console.log('Hi ' + user + '!');
});

So far so good. My array_each function makes my code very readable and does everything I need. For now.

The next time I need to revisit my function is when I need to loop over a data structure and return the first match for a specific criteria. Normally I can use break to end the loop, but my process item isn't actually in the loop. I need a way to signal from my process_item function that the loop needs to trigger a break.

function array_each(array, process_item){

  var item, continue;
 
  for(var index = 0; index < array.length; index++) {
     
     item = array[index];
     continue = process_item( item );

     if(continue === false) break;
  }
}

To stop a loop, all I need to do is return false from my process_item function.

// find a user who can fly

var users = [{name: "Batman", can_fly: false}, {name: "Superman", can_fly: true}, {name: "Wonder Woman", can_fly: true}, {name: "Flash", can_fly: false}, {name: "Cyborg", can_fly: true}];

array_each(users, function(user){

  var continue_searching = true;

  if(user.can_fly){ 
    
    console.log(user.name + ' can fly!');
    continue_searching = false;
  }

  return continue_searching;
});

Best part is that it's still backwards-compatible! I don't need modify my previous process_item functions because the new functionality triggers only when process_item returns false.

This is pretty good as is, but it can be better. Sometimes I need to know item's position in the array. Need to make sure its available when I need it.

function array_each(array, process_item){

  var item, continue_loop;
 
  for(var index = 0; index < array.length; index++) {
     
     item = array[index];
     continue_loop = process_item( item, index );

     if(continue_loop === false) break;
  }
}

I'm passing the current item's index to the process_item function as a second argument. My previous functions aren't broken by this update. Javascript is cool enough to let me pass any amount of arguments to a function, even if the function doesn't use all (or any) of them. Most times I'd be dealing directly with the item, which is why I'm leaving it as the first argument.  The index is passed as a secondary argument so it's there when I need it, but can be safely ignored when constructing process_item functions that don't need it.

// check if and when Wonder Woman logged in

var users_online = ["Batman", "Superman", "Wonder Woman", "Flash", "Cyborg"];

array_each(users_online, function(user, index){
  
  var continue_searching = true;
  var position;

  if(user === "Wonder Woman"){

    continue_searching = false;
    
    if(index === 0){

      position = "first";
    }
 
    else {
      
      position = "after " + users_online[index - 1];
    }

    console.log('Wonder Woman is online! She logged in ' + position);
  }

  return continue_searching;
});

At this point I'm thoroughly pleased. I'm comfortable processing an array, stopping when I want and using my current index in the array to do other things. I'm pretty confident I wouldn't need to modify this.

Unfortunately I set myself up for failure right from the beginning by forgetting something very important: arrays are not the only type of data collection.

Objects are conceptually similar to arrays, but different enough to make all the difference in the world.

1. Object keys are not necessarily numbers.
2. Objects don't come with a built-in length property that tells us how many attributes it has.

This means it's not possible to retrofit array_each to handle objects because it needs to know how many properties it's looping through and walks the array by increasing a numerical index. Bummer.

I need to make an object_each with a similar interface to array_each, so that my process_item functions are identical regardless what type of data structure is being passed.

function object_each( object, process_item){

  var item, continue_loop;

  for(var property in object){

    if( !object.hasOwnProperty(property) ) continue;

      item = object[property];
      continue_loop = process_item( item, property );
      
      if(continue_loop === false) break;
  }
}

Instead of using a for loop that increments the index being accessed in the array, I'm using a for .. in loop that iterates over the properties of an object. object.hasOwnProperty checks if the property being accessed actually belongs to the object or is inherited. I only care about the data directly in the object, so this is how I filter out the other properties objects come loaded with.

I can safely pass the property name to object_each's process_item because accessing an item in an object and an array are interchangeable.

Here's an example of what object_each can do.

// list batman's stats

var batman = {
  "armors": ["batsuit"],
  "equipments": ["batarangs", "smoke bombs", "grappling gun"],
  "vehicles": ["batmobile", "batplane", "batcycle"],
  "allies": ["Robin", "Nightwing", "Batgirl"]
}

object_each(batman, function(section, title){
  
  console.log( '\n\n' + title.toUpperCase() );
  
  array_each(section, function(item, index){

    console.log( (index + 1) + '. ' + item );
  });
});

Looking pretty concise! The code is neat and readable. I could leave it like this, but notice object_each and array_each have the exact same function and interface. I can create a wrapper around both of them. The wrapper will determine which function is more appropriate and use it to process the data structure handed to it. Throw a few checks to make sure everything is kosher and voila! One function to rule them all.

function each(collection, process_item){

  if(!collection || !process_item){    
    throw new Error( 'missing ' + (!collection ? 'data structure' : 'process item function') );
  }

  if(typeof process_item !== 'function'){
    throw new Error('item processor is not a function');
  }

  var collection_type = Object.prototype.toString.call( collection );

  switch( collection_type ){
    
    case '[object Array]':

      array_each(collection, process_item);
      break;

    case '[object Object]':
    default:

      object_each(collection, process_item);
      break;
  } 
}

A few things going on here. First off, each makes sure it is being passed what is needed to work and confirms the item processor function is a function. Next, it detects what type of data is being passed to it to determine which function is appropriate - array_each for arrays and object_each for objects. I set each to default to using object_each. In Javascript, pretty much everything is an object. I can safely assume that if you pass an array, you're interested in the array's content. If you pass any other thing, you're interested in the object's properties. A notable exception is string content-type, which behaves pretty much like an array.

My previous example now looks like this:

// list batman's stats

var batman = {
  "armors": ["batsuit"],
  "equipments": ["batarangs", "smoke bombs", "grappling gun"],
  "vehicles": ["batmobile", "batplane", "batcycle"],
  "allies": ["Robin", "Nightwing", "Batgirl"]
}

each(batman, function(section, title){
  
  console.log( '\n\n' + title.toUpperCase() );
  
  each(section, function(item, index){

    console.log( (index + 1) + '. ' + item );
  });
});

Perfect!

Pulling it all together into a single reusable function results in:

function each(collection, process_item){

  if(!collection || !process_item){    
    throw new Error( 'missing ' + (!collection ? 'data structure' : 'process item function') );
  }

  if(typeof process_item !== 'function'){
    throw new Error('item processor is not a function');
  }

  var collection_type = Object.prototype.toString.call( collection );

  switch( collection_type ){
    
    case '[object Array]':

      array_each(collection, process_item);
      break;

    case '[object Object]':
    default:

      object_each(collection, process_item);
      break;
  }

  function array_each(array, process_item){

    var item, continue_loop;
 
    for(var index = 0; index < array.length; index++) {
     
      item = array[index];
      continue_loop = process_item( item, index );

      if(continue_loop === false) break;
    }
  }

  function object_each( object, process_item){

    var item, continue_loop;

    for(var property in object){

      if( !object.hasOwnProperty(property) ) continue;
      
        item = object[property];
        continue_loop = process_item( item, property );
      
        if(continue_loop === false) break;
    }
  }
}

I can do further optimizations and cleanup, but that's for another day and another post. The current state of each scratches my itch, so there's no immediate need for me to optimize further.


 

I really hope this glimpse into my thinking and coding process has been helpful 🙂