Creating reusable components using function wrappers (with Javascript)

Creating reusable code is hard. I know, you have crappy deadlines coming up and you just need that feature today. But, before you go ahead and just dial in that feature, consider the future implications of it. Your code doesn’t die tomorrow, it doesn’t die next week, your code lives as long as the product does. Keeping that code base small and understandable is what ensures that working on the project remains as maintainable as possible. That’s the real reason why reusability is so important. However, that’s a discussion for another day. Today I’m covering one part of the puzzle, which is integrating wrapper functions.

If you want to look at the full body of this code, all of it can be found on my github here.

What is a wrapper function

Put simply, a wrapper function is a function which wraps another function. The underlying concept to this is that you write a function that makes functions. You folks are smart, so let’s get straight to our example. Let’s make a function that only returns true if a user has a particular role.

exports.onlyAllow = function(roles)
{
  return function(user) {return roles.includes(user.role);}
} 

What does this do?

So just now, we’ve created a function that expects a number of roles that returns a function, that expects a user which, if the user has one of the prescribed roles, it returns true, otherwise, it returns false. So an example use of it would be

const {onlyAllow} = require('good.js');
let onlyAdmins = onlyAllow(['admin']);//Creates our function
console.log(onlyAdmins({role:'admin'})//Resolves to true

Where are you going with this?

I’m trying to show off the idea of modeling specific behavior instead of specific cases. So in a theoretical app, a specific behavior may be “only allow these things”, which you saw above, another example behavior that I don’t think is as useful or safe for that matter (Meaning admittedly this is kind of stupid) would be something that doesn’t allow certain roles.

exports.dontAllow = function(roles)
{
  return function(user) {return !roles.includes(user.role);}
} 

This particular tidbit may be useful in some middlewares for some apps, but that’s not the point. What you should grasp here is the idea that we’re modeling this particular behavior not a specific case.

What do you mean by a specific case?

When I say that, I’m referring to some yahoo thinking that it’s ‘self documenting’ to do something like this.

exports.isAdmin = function(user)
{     
  return user.role == 'admin'
} 
exports.isWizard = function(user)
{     
  return user.role == 'wizard'
} 
exports.isLizard = function(user)
{     
  return user.role == 'lizard'
} 

Now, what we’ve effectively done here is hardcode every single last role in our made up app. The issue with this is that each of these things don’t reflect a specific behavior we want to capture, they reflect a specific instance of a behavior. The next logical step here would be to also code isNotAWizard, isNotALizard, and isNotAnAdmin, which I’ll spare you for the sake of brevity. I know that reading this you’re going to tell yourself “I’d never code that” I’ve said the same thing and sometimes crunch time rolls around and it just happens. We all do it, it’s a normal part of day to day programming or even happens unknowingly. We’re not wizards (But some edge cases allow us to be lizards) and are unable to see into the future. Broken windows are a part of life, but we should always be vigilant of them. Just look at these mocha unit tests as an example. This is a bit of a long code block, so feel free to only give it a cursory look

const expect = require("chai").expect;

describe("Check Authorization Level",function()
{
  describe("bad wrapper example", function()//6ix9ine
  {
    
    let admin = {name:'brad',role:'admin'};
    let wizard = {name:'brad',role:'wizard'};
    let lizard = {name:'brad',role:'lizard'};
    it('tests for isAdmin',function()
    {
      expect(role.isAdmin(admin)).to.equal(true);
      expect(role.isAdmin(wizard)).to.equal(false);
    })
    it('tests for isWizard',function()
    {
      expect(role.isWizard(wizard)).to.equal(true);
      expect(role.isWizard(lizard)).to.equal(false);
    })
    it('tests for isLizard',function()
    {
      expect(role.isLizard(lizard)).to.equal(true);
      expect(role.isLizard(wizard)).to.equal(false);
    }
  })
  
  describe("good wrapper example", function()//The real slim shady
  {
    let admin = {name:'brad',role:'admin'};
    let wizard = {name:'brad',role:'wizard'};
    let lizard = {name:'brad',role:'lizard'};
    let onlyAdmins = onlyAllow(['admin']);
    let noLizards = dontAllow(['lizard']);

    it('only allow tests',function()
    {
      expect(onlyAdmins(admin)).to.equal(true);
      expect(onlyAdmins(lizard)).to.equal(false);
    })

    it('Dont allow tests',function()
    {
      expect(noLizards(wizard)).to.equal(true);
      expect(noLizards(lizard)).to.equal(false);

    })
  })
})

Our bad testing overhead is a bit larger than our good example. Take the long view here and consider what these two tests will look like with four cases; five cases;ten cases! We don’t need to update our good test cases because all they test are our two behaviors: “Allow this” and “Don’t allow this”. In our bad example, we have to edit our codebase directly to add in this behavior (already kind of icky), which grows it by a few lines, after that we have to add our tests with also grows it by 3 lines (5 if you want to include “isNotWizard” and the like). That’s a lot of growth of nothing vs 10+ lines per case, all because someone didn’t recognize a good place to use a wrapper function.

To Wrap Up (ha)

Use wrapper functions. They’re great at automating specific cases into code behaviors. I know a place that I use them extensively are ExpressJS middlewares, but try to think back to some earlier projects where you accidentally hardcoded each case and consider if you could have employed wrapper functions to alleviate that issue.

Another thing to keep in mind this doesn’t just apply to javascript. Many other languages support similar features, such as decorator functions in Python. If you want to employ something like this in your language of preference, look it up. There’s a wealth of options in many languages to cover this topic.

Consuming Axios in a Vue + Vuex project

This tutorial focus will show you how to assimilate Axios into your tool belt for use in future projects to help save you time and headache.

Prereqs:

  • Basic knowledge of
    • Vue.Js
    • Vuex
    • Axios
  • A preexisting Vuejs app with Vuex

What are we doing and why?

We are taking the Axios plugin for Vuejs and baking it into our own custom GET, POST, PUT and DELETE functions for future projects in a scalable, reusable manner, that will help stop some bad smells from appearing in your code.

Getting Started

Make a new file called “CRUD.js” This is where we will store all of our CRUD functions. let’s create a basic “GET” wrapper for Axios with some minimal error handling.

import axios from "axios"

const GetFunction = async function(route,params,headers)
{
    try
    {
        var results = await axios.get(route,{params,headers});
        return results.data;    
    }
    catch(error)
    {
        return error.response.data;
    }
}
export const Get = GetFunction;

Doing this alone only gives us the benefit of our own custom error handling. Which is nice but we can do much better. In the app where this solution was ultimately handled, the headers from the website did not change between requests, neither did the route. All we have to do to exploit this is add a few lines to our code so we may remove a few arguments from this function call

import axios from "axios"
import store from '@/store/store'

const GetFunction = async function(params)
{
    var address = store.state.address; 
    var headers = store.state.headers;
    try
    {
        var results = await axios.get(address,{params,headers});
        return results.data;    
    }
    catch(error)
    {
        return error.response.data;
    }
    
}

But why?

Basically this eliminates some headache for us down the line. By referring to our state we can refer to our API backend and our token simply by calling our “Get” export from our module. In the event that our API’s address changes, such as when we have a different IP/address from development to deployment, we don’t need to change every single Axios call in our project. Simply change what our state for what the address is, and then we can move on to deployment with a great deal less of worry on our shoulders.