Having fun with ES6 proxies
Tags: es6, javascript, webdev
Published: 2019-11-13 14:38:39

---
title: Having fun with ES6 proxies
published: true
date: 2019-11-13 14:00:35 UTC
tags: es6,javascript,webdev
canonical_url: https://blog.logrocket.com/having-fun-with-es6-proxies/
cover_image: https://thepracticaldev.s3.amazonaws.com/i/z4hwm967avarst2sc1n2.jpeg
---
**Written by [Maciej Cieślar](https://blog.logrocket.com/author/maciejcieslar/)**✏️

`Proxy` is one of the most overlooked concepts introduced in the ES6 version of JavaScript.

Admittedly, it isn’t particularly useful on a day-to-day basis, but it is bound to come in handy at some point in your future.

## The basics

The `Proxy` object is used to define a custom behavior for fundamental operations such as property lookup, assignment, and function invocation.

The most basic example of a proxy would be:

```jsx
const obj = {
 a: 1,
 b: 2,
};

const proxiedObj = new Proxy(obj, {
 get: (target, propertyName) => {
   // get the value from the "original" object
   const value = target[propertyName];

   if (!value && value !== 0) {
     console.warn('Trying to get non-existing property!');

     return 0;
   }

   // return the incremented value
   return value + 1;
 },
 set: (target, key, value) => {
   // decrement each value before saving
   target[key] = value - 1;

   // return true to indicate successful operation
   return true;
 },
});

proxiedObj.a = 5;

console.log(proxiedObj.a); // -> incremented obj.a (5)
console.log(obj.a); // -> 4

console.log(proxiedObj.c); // -> 0, logs the warning (the c property doesn't exist)
```

We have intercepted the default behavior of both `get` and `set` operations by defining the handlers with their respective names in the object provided to the proxy constructor. Now each `get` operation will return the incremented value of the property, while `set` will decrement the value before saving it in the target object.

What’s important to remember with proxies is that once a proxy is created, it should be the only way to interact with the object.

### Different kinds of traps

There are many traps (handlers that intercept the object’s default behavior) aside from `get` and `set`, but we won’t be using any of them in this article. With that being said, if you are interested in reading more about them, here’s the [documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy).

## Having fun

Now that we know how proxies work, let’s have some fun with them.

### Observing object’s state

As it has been stated before it is very easy to intercept operations with proxies. To observe an object’s state is to be notified every time there’s an assignment operation.

```jsx
const observe = (object, callback) => {
 return new Proxy(object, {
   set(target, propKey, value) {
     const oldValue = target[propKey];
   
     target[propKey] = value;

     callback({
       property: propKey,
       newValue: value,
       oldValue,
     });

     return true;
   }
 });
};

const a = observe({ b: 1 }, arg => {
 console.log(arg);
});

a.b = 5; // -> logs from the provided callback: {property: "b", oldValue: 1, newValue: 5}
```

And that’s all we have to do — invoke the provided callback every time the `set` handler is fired.

As an argument to the `callback`, we provide an object with three properties: the name of the changed property, the old value, and the new value.

Prior to executing the `callback`, we assign the new value in the target object so the assignment actually takes place. We have to return `true` to indicate that the operation has been successful; otherwise, it would throw a `TypeError`.

Here’s a [live example](https://codesandbox.io/s/proxy-observe-s5sjv?fontsize=14).

### Validating properties on `set`

If you think about it, proxies are a good place to implement validation — they are not tightly coupled with the data itself. Let’s implement a simple validation proxy.

As in the previous example, we have to intercept the `set` operation. We would like to end up with the following way of declaring data validation:

```jsx
const personWithValidation = withValidation(person, {
 firstName: [validators.string.isString(), validators.string.longerThan(3)],
 lastName: [validators.string.isString(), validators.string.longerThan(7)],
 age: [validators.number.isNumber(), validators.number.greaterThan(0)]
});
```

In order to achieve this, we define the `withValidation` function like so:

```jsx
const withValidation = (object, schema) => {
 return new Proxy(object, {
   set: (target, key, value) => {
     const validators = schema[key];

     if (!validators || !validators.length) {
       target[key] = value;

       return true;
     }

     const shouldSet = validators.every(validator => validator(value));

     if (!shouldSet) {
       // or get some custom error
       return false;
     }

     target[key] = value;
     return true;
   }
 });
};
```

First we check whether or not there are `validators` in the provided schema for the property that is currently being assigned — if there aren’t, there is nothing to validate and we simply assign the value.

If there are indeed `validators` defined for the property, we assert that all of them return `true` before assigning. Should one of the validators return `false`, the whole `set` operation returns false, causing the proxy to throw an error.

The last thing to do is to create the `validators` object.

```jsx
const validators = {
 number: {
   greaterThan: expectedValue => {
     return value => {
       return value > expectedValue;
     };
   },
   isNumber: () => {
     return value => {
       return Number(value) === value;
     };
   }
 },
 string: {
   longerThan: expectedLength => {
     return value => {
       return value.length > expectedLength;
     };
   },
   isString: () => {
     return value => {
       return String(value) === value;
     };
   }
 }
};
```

The `validators` object contains validation functions grouped by the type they should validate. Each validator on invocation takes the necessary arguments, like `validators.number.greaterThan(0)`, and returns a function. The validation happens in the returned function.

We could extend the validation with all kinds of amazing features, such as virtual fields or throwing errors from inside the validator to indicate what went wrong, but that would make the code less readable and is outside the scope of this article.

Here’s a [live example](https://codesandbox.io/s/proxy-validation-2858r?fontsize=14).

### Making code lazy

For the final — and hopefully most interesting — example, let’s create a proxy that makes all the operations lazy.

Here’s a very simple class called `Calculator`, which contains a few basic arithmetic operations.

```jsx
class Calculator {
 add(a, b) {
   return a + b;
 }

 subtract(a, b) {
   return a - b;
 }

 multiply(a, b) {
   return a * b;
 }

 divide(a, b) {
   return a / b;
 }
}
```

Now normally, if we ran the following line:

```jsx
new Calculator().add(1, 5) // -> 6
```

The result would be 6.

The code is executed on the spot. What we would like is to have the code wait for the signal to be run, like a `run` method. This way the operation will be postponed until it is needed — or not executed at all if there is never a need.

So the following code, instead of 6, would return the instance of the `Calculator` class itself:

```jsx
lazyCalculator.add(1, 5) // -> Calculator {}
```

Which would give us another nice feature: method chaining.

```jsx
lazyCalculator.add(1, 5).divide(10, 10).run() // -> 1
```

The problem with that approach is that in `divide`, we have no clue of what the result of `add` is, which makes it kind of useless. Since we control the arguments, we can easily provide a way to make the result available through a previously defined variable — `$`, for example.

```jsx
lazyCalculator.add(5, 10).subtract($, 5).multiply($, 10).run(); // -> 100
```

`$` here is just a constant `Symbol`. During execution, we dynamically replace it with the result returned from the previous method.

```jsx
const $ = Symbol('RESULT_ARGUMENT');
```

Now that we have a fair understanding of what do we want to implement, let’s get right to it.

Let’s create a function called `lazify`. The function creates a proxy that intercepts the `get` operation.

```jsx
function lazify(instance) {
 const operations = [];

 const proxy = new Proxy(instance, {
   get(target, propKey) {
     const propertyOrMethod = target[propKey];

     if (!propertyOrMethod) {
       throw new Error('No property found.');
     }

     // is not a function
     if (typeof propertyOrMethod !== 'function') {
       return target[propKey];
     }

     return (...args) => {
       operations.push(internalResult => {
         return propertyOrMethod.apply(
           target,
           [...args].map(arg => (arg === $ ? internalResult : arg))
         );
       });

       return proxy;
     };
   }
 });

 return proxy;
}
```

Inside the `get` trap, we check whether or not the requested property exists; if it doesn’t, we throw an error. If the property is not a function, we return it without doing anything.

Proxies don’t have a way of intercepting method calls. Instead, they are treating them as two operations: the `get` operation and a function invocation. Our `get` handler has to act accordingly.

Now that we are sure the property is a function, we return our own function, which acts as a wrapper. When the wrapper function is executed, it adds yet another new function to the operations array. The wrapper function has to return the proxy to make it possible to chain methods.

Inside the function provided to the operations array, we execute the method with the arguments provided to the wrapper. The function is going to be called with the result argument, allowing us to replace all the `$` with the result returned from the previous method.

This way we delay the execution until requested.

Now that we have built the underlying mechanism to store the operations, we need to add a way to run the functions — the `.run()` method.

This is fairly easy to do. All we have to do is check whether the requested property name equals run. If it does, we return a wrapper function (since run acts as a method). Inside the wrapper, we execute all the functions from the operations array.

The final code looks like this:

```jsx
const executeOperations = (operations, args) => {
 return operations.reduce((args, method) => {
   return [method(...args)];
 }, args);
};

const $ = Symbol('RESULT_ARGUMENT');

function lazify(instance) {
 const operations = [];

 const proxy = new Proxy(instance, {
   get(target, propKey) {
     const propertyOrMethod = target[propKey];

     if (propKey === 'run') {
       return (...args) => {
         return executeOperations(operations, args)[0];
       };
     }

     if (!propertyOrMethod) {
       throw new Error('No property found.');
     }

     // is not a function
     if (typeof propertyOrMethod !== 'function') {
       return target[propKey];
     }

     return (...args) => {
       operations.push(internalResult => {
         return propertyOrMethod.apply(
           target,
           [...args].map(arg => (arg === $ ? internalResult : arg))
         );
       });

       return proxy;
     };
   }
 });

 return proxy;
}
```

The `executeOperations` function takes an array of functions and executes them one by one, passing the result of the previous one to the invocation of the next one.

And now for the final example:

```jsx
const lazyCalculator = lazify(new Calculator());

const a = lazyCalculator
 .add(5, 10)
 .subtract($, 5)
 .multiply($, 10);

console.log(a.run()); // -> 100
```

If you are interested in adding more functionality I have added a few more features to the `lazify` function — asynchronous execution, custom method names, and a possibility to add custom functions through the `.chain()` method. Both versions of the `lazify` function are available in the [live example](https://codesandbox.io/s/lazy-proxy-q7tbn?fontsize=14).

## Summary

Now that you have seen proxies in action, I hope that you could find a good use for them in your own codebase.

Proxies have many more interesting uses than those covered here, such as implementing negative indices and catching all the nonexistent properties in an object. Be careful, though: proxies are a bad choice when [performance](http://thecodebarbarian.com/thoughts-on-es6-proxies-performance) is an important factor.

* * * 
**Editor's note:** Seeing something wrong with this post? You can find the correct version [here](https://blog.logrocket.com/having-fun-with-es6-proxies/).

## Plug: [LogRocket](https://logrocket.com/signup/), a DVR for web apps
 
![LogRocket Dashboard Free Trial Banner](https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/1d0cd-1s_rmyo6nbrasp-xtvbaxfg.png?resize=1200%2C677&ssl=1)
 
[LogRocket](https://logrocket.com/signup/) is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
 
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.
 
[Try it for free](https://logrocket.com/signup/).
* * *
The post [Having fun with ES6 proxies](https://blog.logrocket.com/having-fun-with-es6-proxies/) appeared first on [LogRocket Blog](https://blog.logrocket.com).