Real World Use Cases for JavaScript Proxies

Introduction to Proxy

In programming terms, a proxy is an entity that acts on behalf of some other entity. A proxy server sits in between a client and server and acts as a client for server and vice versa. The job of any proxy is to intercept incoming requests/calls and forward it upstream. This interception allows proxy to add logic and change behavior of incoming and outgoing requests.

Javascript proxy is something very similar; it sits between your actual object and the code trying to access this object.

According to the MDN Web Docs:

The Proxy object is used to define custom behavior for fundamental operations (e.g., property lookup, assignment, enumeration, function invocation, etc.).

Terminologies

There are three terms we need to know before we can implement a proxy:

Target

Target is the actual object our proxy sits in front of and virtualize. This can be any javascript object.

Traps

Traps are methods that intercept the call to target when a property or method is called. Many defined traps can be implemented as a trap.

Handler

A handler is a placeholder object where all traps live. You can think of it as an object with key beings traps and values being functions implementing those traps.

Let’s look at a basic example :

//movie is a target
const movie = {
	name: "Pulp Fiction",
	director: "Quentin Tarantino"
};

//this is a handler
const handler = {
	//get is a trap
	get: (target, prop) => {
		if (prop === 'director') {
			return 'God'
		}
		return target[prop]
	},

	set: function (target, prop, value) {
		if (prop === 'actor') {
			target[prop] = 'John Travolta'
		} else {
			target[prop] = value
		}
	}
};

const movieProxy = new Proxy(movie, handler);

console.log(movieProxy.director); //God

movieProxy.actor = "Tim Roth";
movieProxy.actress = "Uma Thurman";

console.log(movieProxy.actor); //John Travolta
console.log(movieProxy.actress); //Uma Thurman

The output of the above code execution will be :

God
John Travolta
Uma Thurman

In the above example, our target object was the movie; we implemented a handler with a get and a set trap. We added a logic that if we are accessing the director key, we should return the string God instead of the actual value. Similarly, we added a set trap that will intercept all the writes to the target object and change the value to John Travolta if the key is an actor.

Real-world use cases

Although it is not as well known as other ES2015 features, the proxy has many uses, few of which, like default values for all properties of target, might be obvious now. Let us take a look at more real-world scenarios where we can use proxies.

Validations

Since we can intercept writes to an object, we can validate the value we are trying to set on the object. Let us take an example :

const handler = {
	set: function (target, prop, value) {
		const houses = ['Stark', 'Lannister'];
		if (prop === 'house' && !(houses.includes(value))) {
			throw new Error(`House ${value} does not belong to allowed ${houses}`)
		}
		target[prop] = value
	}
};

const gotCharacter = new Proxy({}, handler);

gotCharacter.name = "Jamie";
gotCharacter.house = "Lannister";

console.log(gotCharacter);

gotCharacter.name = "Oberyn";
gotCharacter.house = "Martell";

The execution of the above code will result in the following output :

{ name: 'Jamie', house: 'Lannister' }
Error: House Martell does not belong to allowed Stark,Lannister

In the above example, we restrict that the value allowed for the property house can only be one of the allowed houses. We can even use this approach to create read-only objects; all we need to do this throw inside the set trap.

Side Effects

We can use proxies to create side effects on a property read/write. The idea is to trigger some function if a particular property is accessed or written. Let us take an example:

const sendEmail = () => {
	console.log("sending email after task completion")
};


const handler = {
	set: function (target, prop, value) {
		if (prop === 'status' && value === 'complete') {
			sendEmail()
		}
		target[prop] = value
	}
};

const tasks = new Proxy({}, handler);

tasks.status = "complete";

The execution of the above code will result in the following output:

sending email after task completion

Here we are intercepting writes to property status, and if the status is complete, we are triggering a side effect function. One cool implementation of this is in Sindre Sorhus‘s on-change package.

Caching

As we can intercept the access to object properties, we can build in-memory caches to only return values for an object if it isn’t expired. Let’s look at an example :

const cacheTarget = (target, ttl = 60) => {
	const CREATED_AT = Date.now();
	const isExpired = () => (Date.now() - CREATED_AT) > (ttl * 1000);
	const handler = {
		get: (target, prop) => isExpired() ? undefined : target[prop]
	};
	return new Proxy(target, handler)
};

const cache = cacheTarget({age: 25}, 5);

console.log(cache.age);

setTimeout(() => {
	console.log(cache.age)
}, 6 * 1000);

The execution of the above code will result in the following output :

25
undefined

Here we create a function that returns a proxy and the handler for that proxy first checks if the object is expired or not. We can extend this functionality to have per key-based TTLs and more.

Drawbacks

While proxies are somewhat magical but there few drawbacks with proxies which we need to be careful about.

  1. Performance can take a drastic hit when using proxies and hence should be avoided when writing a critical performance code.
  2. Given an object, there is no way of knowing if this is a proxy object or target object.
  3. Lastly, proxies do not necessarily lead to a very clean and easily understandable code.

Conclusion

Proxies are incredibly powerful and can be used and abused for a wide array of things. In this article, we looked at what proxies are, how to implement them, few real-world use cases of them, and their drawbacks.

Latest articles

How to do Destructuring in JavaScript

What is Destructuring? At its core, destructuring is the idea that we are pulling out values individually from a...

Writable Streams, Streams Piping, and Error Handling in Node.js

A Writable Stream A writable stream is a stream of data that is created to write some streaming data....

Here are Six Front-End Development Channels on YouTube

The Coding Train https://www.youtube.com/watch?v=bKEaK7WNLzM If you love programming then this is the...

Real World Use Cases for JavaScript Proxies

Introduction to Proxy In programming terms, a proxy is an entity that acts on behalf of some other entity....
14.0k Followers
Follow

Related articles

Leave a reply

Please enter your comment!
Please enter your name here