3 things to do with ES6 proxies that will enhance your Objects

3 things to do with ES6 proxies that will enhance your Objects

Proxy is one of the most overlooked concepts introduced in ES6 version of JavaScript. In this article, I want to cover three things you can do with ES6 proxies that will enhance your objects specifically.

One of the aspects of programming I love the most is meta-programming, which references the ability to change the basic building blocks of a language, using that language itself to make the changes. Developers use this technique to enhance the language or even, in some cases, to create new custom languages known as Domain Specific Language (or DSL for short).

Many languages already provide deep levels of meta-programming, but JavaScript was missing some key aspects.

Yes, it’s true, JavaScript is flexible enough that it allows you to stretch the language quite a bit, considering how you can add attributes to an object during run-time, or how you can easily enhance the behavior of a function by passing it different functions as a parameter. But with all of that, there were still some limits, which the new proxies now allow us to surpass.

In this article, I want to cover three things you can do with proxies that will enhance your objects specifically. Hopefully, by the end of it, you’ll be able to expand my code and maybe apply it yourself to your own needs!

How do proxies work? A quick intro

Proxies basically wrap your objects or functions around a set of traps, and once those traps are triggered, your code gets executed. Simple, right?

The traps we can play around with are: 3 ways to use ES6 proxies

Those are the standard traps, you’re more than welcome to check out Mozilla’s Web Docs for more details on each and every one of them since I’ll be focusing on a subset of those for this article.

That being said, the way you create a new proxy or, in other words, the way you wrap your objects or function calls with a proxy, looks something like this:

let myString = new String("hi there!")
let myProxiedVar = new Proxy(myString, {
  has: function(target, key) {
    return target.indexOf(key) != -1;
  }
})
console.log("i" in myString)
// false
console.log("i" in myProxiedVar)
//true

That’s the basis of a proxy, I’ll be showing more complex examples in a second, but they’re all based on the same syntax.

Proxies vs Reflect

But before we start looking at the examples, I wanted to quickly cover this question, since it’s one that gets asked a lot. With ES6 we didn’t just get proxies, we also got the [Reflect](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect) object, which at first glance, does exactly the same thing, doesn’t it?

The main confusion comes because most documentation out there, states that Reflect has the same methods as the proxy handlers we saw above (i.e the traps). And although that is true, there is a 1:1 relationship there, the behavior of the Reflect object and its methods are more alike to that of the Object global object.

For example, the following code:

const object1 = {
  x: 1,
  y: 2
};

console.log(Reflect.get(object1, 'x'));

Will return a 1, just as if you would’ve directly tried to access the property. So instead of changing the expected behavior, you can just execute it with a different (and in some cases, more dynamic) syntax.

Enhancement #1: dynamic property access

Let’s now look at some examples. To start things off, I want to show you how you can provide extra functionality to the action of retrieving a property’s value.

What I mean by that is, assuming you have an object such as:

class User {
  constructor(fname, lname) {
    this.firstname =  fname
    this.lastname = lname
  }
}

You can easily get the first name, or the last name, but you can’t simply request the full name all at once. Or if you wanted to get the name in all caps, you’d have to chain method calls. This is by no means, a problem, that’s how you’d do it in JavaScript:

let u = new User("fernando", "doglio")
console.log(u.firstname + " " + u.lastname)
//would yield: fernando doglio
console.log(u.firstname.toUpperCase())
//would yield: FERNANDO

But with proxies, there is a way to make your code more declarative. Think about it, what if you could have your objects support statements such as:

let u = new User("fernando", "doglio")
console.log(u.firstnameAndlastname)
//would yield: fernando doglio
console.log(u.firstnameInUpperCase)
//would yield: FERNANDO

Of course, the idea would be to add this generic behavior to any type of object, avoiding manually creating the extra properties and polluting the namespace of your objects.

This is where proxies come into play, if we wrap our objects and set a trap for the action of getting the value of a property, we can intercept the name of the property and interpret it to get the wanted behavior.

Here is the code that can let us do just that:

function EnhanceGet(obj) {
  return new Proxy(obj, {
    get(target, prop, receiver) {

      if(target.hasOwnProperty(prop)) {
          return target[prop]
      }
      let regExp = /([a-z0-9]+)InUpperCase/gi
      let propMatched = regExp.exec(prop)

      if(propMatched) {
        return target[propMatched[1]].toUpperCase()
      } 

      let ANDRegExp = /([a-z0-9]+)And([a-z0-9]+)/gi
      let propsMatched = ANDRegExp.exec(prop)
      if(propsMatched) {
          return [target[propsMatched[1]], target[propsMatched[2]]].join(" ")
      }
      return "not found"
     }
  });
}

We’re basically setting up a proxy for the get trap, and using regular expressions to parse the property names. Although we’re first checking if the name actually meets a real property and if that’s the case, we just return it. Then, we check for the matches on the regular expressions, capturing, of course, the actual name in order to get that value from the object to then further process it.

Now you can use that proxy with any object of your own, and the property getter will be enhanced!

Enhancement #2: custom error handling for invalid property names

Next, we have another small but interesting enhancement. Whenever you try to access a property that doesn’t exist on an object, you don’t really get an error, JavaScript is permissive like that. All you get is undefined returned instead of its value.

What if, instead of getting that behavior, we wanted to customize the returned value, or even throw an exception since the developer is trying to access a non-existing property.

We could very well use proxies for this, here is how:

function CustomErrorMsg(obj) {
  return new Proxy(obj, {
    get(target, prop, receiver) {
      if(target.hasOwnProperty(prop)) {
          return target[prop]
      }
      return new Error("Sorry bub, I don't know what a '" + prop + "' is...")
     }
  });
}
Now, that code will cause the following behavior:
> pa = CustomErrorMsg(a)
> console.log(pa.prop)
Error: Sorry bub, I don't know what a 'prop' is...
    at Object.get (repl:7:14)
    at repl:1:16
    at Script.runInThisContext (vm.js:91:20)
    at REPLServer.defaultEval (repl.js:317:29)
    at bound (domain.js:396:14)
    at REPLServer.runBound [as eval] (domain.js:409:12)
    at REPLServer.onLine (repl.js:615:10)
    at REPLServer.emit (events.js:187:15)
    at REPLServer.EventEmitter.emit (domain.js:442:20)
    at REPLServer.Interface._onLine (readline.js:290:10)

We could be more extreme like I mentioned, and do something like:

function HardErrorMsg(obj) {
  return new Proxy(obj, {
    get(target, prop, receiver) {
      if(target.hasOwnProperty(prop)) {
          return target[prop]
      }
      throw new Error("Sorry bub, I don't know what a '" + prop + "' is...")
     }
  });
}

And now we’re forcing developers to be more mindful when using your objects:

> a = {}
> pa2 = HardErrorMsg(a)
> try {
... console.log(pa2.property)
 } catch(e) {
... console.log("ERROR Accessing property: ", e)
 }
ERROR Accessing property:  Error: Sorry bub, I don't know what a 'property' is...
    at Object.get (repl:7:13)
    at repl:2:17
    at Script.runInThisContext (vm.js:91:20)
    at REPLServer.defaultEval (repl.js:317:29)
    at bound (domain.js:396:14)
    at REPLServer.runBound [as eval] (domain.js:409:12)
    at REPLServer.onLine (repl.js:615:10)
    at REPLServer.emit (events.js:187:15)
    at REPLServer.EventEmitter.emit (domain.js:442:20)
    at REPLServer.Interface._onLine (readline.js:290:10)

Heck, using proxies you could very well add validations to your sets, making sure you’re assigning the right data type to your properties.

There is a lot you can do, using the basic behavior shown above in order to mold JavaScript to your particular desire.

Enhancement #3: dynamic behavior based on method names

The last example I want to cover is similar to the first one. Whether before we were able to add extra functionality by using the property name to chain extra behavior (like with the “InUpperCase” ending), now I want to do the same for method calls. This would allow us to not only extend the behavior of basic methods just by adding extra bits to its name, but also receive parameters associated with those extra bits.

Let me give you an example of what I mean:

myDbModel.findById(2, (err, model) => {
  //....
})

That code should be familiar to you if you’ve used a database ORM in the past (such as Sequelize or Mongoose, for example). The framework is capable of guessing what your ID field called, based on the way you set up your models. But what if you wanted to extend that into something like:

 myDbModel.findByIdAndYear(2, 2019, (err, model) => {
  //...
})

And take it a step further:

myModel.findByNameAndCityAndCountryId("Fernando", "La Paz", "UY", (err, model) => {
  //...
})

We can use proxies to enhance our objects into allowing for such behavior, allowing us to provide extended functionality without having to manually add these methods. Besides, if your DB models are complex enough, all the possible combinations become too much to add, even programmatically, our objects would end up with too many methods that we’re just not using. This way we’re making sure we only have one, catch-all method that takes care of all combinations.

In the example, I’m going to be creating a fake MySQL model, simply using a custom class, to keep things simple:

var mysql      = require('mysql');
var connection = mysql.createConnection({
  host     : 'localhost',
  user     : 'user',
  password : 'pwd',
  database : 'test'
});

connection.connect();

class UserModel {
    constructor(c) {
        this.table = "users"
        this.conn = c
    }
}

The properties on the constructor are only for internal use, the table could have all the columns you’d like, it makes no difference.

let Enhacer = {
    get : function(target, prop, receiver) {
      let regExp = /findBy((?:And)?[a-zA-Z_0-9]+)/g
      return function() { //
          let condition = regExp.exec(prop)
          if(condition) {
            let props = condition[1].split("And")
            let query =  "SELECT * FROM " + target.table + " where " + props.map( (p, idx) => {
                let r = p + " = '" + arguments[idx] + "'"
                return r
            }).join(" AND ")
            return target.conn.query(query, arguments[arguments.length - 1])
          }
      }
    }
}

Now that’s just the handler, I’ll show you how to use it in a second, but first a couple of points:

  • Notice the regular expression. We’ve been using them in the previous examples as well but they were simpler. Here we need a way to catch a repetitive pattern: findBy + propName + And as many times as we need.
  • With the map call, we’re making sure we map every prop name to the value we received. And we get the actual value using the arguments object. Which is why the function we’re returning can’t be an arrow function (those don’t have the arguments object available).
  • We’re also using the target’s table property, and its conn property. The target is our object, as you’d expect, and that is why we defined those back in the constructor. In order to keep this code generic, those props need to come from outside.
  • Finally, we’re calling the query method with two parameters, and we’re assuming the last argument our fake method received, is the actual callback. That way we just grab it and pass it along.

That’s it, the TL;DR of the above would be: we’re transforming the method’s name into a SQL query and executing it using the actual query method.

Here is how you’d use the above code:

let eModel = new Proxy(new UserModel(connection), Enhacer) //create the proxy here

eModel.findById("1", function(err, results) { //simple method call with a single parameter
    console.log(err)
    console.log(results)
})
eModel.findByNameAndId('Fernando Doglio', 1, function(err, results) { //extra parameter added
    console.log(err)
    console.log(results)
    console.log(results[0].name)
})

That is it, after that the results are used like you would, nothing extra is required.

Conclusion

That would be the end of this article, hopefully, it helped clear out a bit of the confusion behind proxies and what you can do with them. Now let your imagination run wild and use them to create your own version of JavaScript!

ES6 javascript web-development

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

Hire Web Developer

Looking for an attractive & user-friendly web developer? HourlyDeveloper.io, a leading web, and mobile app development company, offers web developers for hire through flexible engagement models. You can **[Hire Web...

Why Web Development is Important for your Business

With the rapid development in technology, the old ways to do business have changed completely. A lot more advanced and developed ways are ...

Important Reasons to Hire a Professional Web Development Company

    You name the business and I will tell you how web development can help you promote your business. If it is a startup or you seeking some...

Hire Dedicated eCommerce Web Developers | Top eCommerce Web Designers

Build your eCommerce project by hiring our expert eCommerce Website developers. Our Dedicated Web Designers develop powerful & robust website in a short span of time.

How long does it take to develop/build an app?

This article covers A-Z about the mobile and web app development process and answers your question on how long does it take to develop/build an app.