Method Middlewares with JavaScript Proxies

3 minute read for ~600 words

You have an object that is a collection of methods:

const myFunctions = {
    constant: (x) => 1,
    linear: (x) => x,
    sine: (x) => Math.sin(Math.PI * x),
    cos: (x) => Math.cos(Math.PI * x),
};

And you want to do the same operation based on the result of the methods:

const scaleToRange = (y, [yi, yf]) => y * (yf - yi) + yi;

const y = myFunctions.sine(x);
const yScaled = scaleToRange(y, [Y_MIN, Y_MAX]);

No problem at all. 100/100 code. No notes. Commit. Push. Rejoice your life.

But you slowly realize that you don’t want to see the unnecessary yScaled declaration because it is not relevant to your function flow1. For whatever elitist reason that will make you feel like a better developer, you think that it’d better if you could just abstract this scaleToRange function into the object itself.

// Your dream code which is objectively worse
const y = myFunctions.sine(x, [Y_MIN, Y_MAX]);

You realize what you want is a middleware to your method calls. So you resort to the obvious JavaScript abuse that you love doing so much:

const myFunctions = Object.fromEntries(
	Object.entries({
		constant: (x) => 1,
		linear: (x) => x,
		sine: (x) => Math.sin(Math.PI * x),
		cos: (x) => Math.cos(Math.PI * x),
	}).map(([key, fn]) => [key, (x, [yi, yf]) => (yf - yi) * fn(x) + yi])
);

Now there’s another problem. “3 iterations to declare the object?”, you scoff. Being a JavaScript developer, performance is of utmost priority to you. A dormant section in your brain lights up immediately. “Proxies!”, you exclaim, “aren’t they supposed to do something like this?”. A proxy technically is a middleware after all.

You instinctively open your Bible and lookup for the documentation on JavaScript proxies, simultaneously fighting your sane sub-conscious thoughts, that are fragments of an article long forgotten about how you probably don’t need proxies.

After a few minutes, you come up with this:

const myFunctions = new Proxy(
	{
		constant: () => 1,
		linear: (x) => x,
		sine: (x) => Math.sin(Math.PI * x),
		cos: (x) => Math.cos(Math.PI * x),
	},
	{
		get: (target, key) => {
			const fn = target[key];

			return (x, [yi, yf]) => {
				const y = fn(x);
				return (yf - yi) * y + yi;
			};
		},
	},
);

Explanation

A Proxy in JavaScript is used to intercept and redefine fundamental operations of an object that it wraps. It has two parameters. A target - which is your original object you want to proxy stuff to and from. And a handler - which is an object that contains how you proxy the target object. It returns the modified object.

In the above code, we are creating a Proxy object for the myFunctions object. The handler object has a get method which is called whenever a property is accessed on the myFunctions object. The get method receives the target object and the key of the property that is being accessed. That enables us to intercept the property access and manipulate it before returning it.


You hear Jerusalem bells ringing. The Roman cavalry choirs are singing. A portal opens up next to you. Brendan Eich steps out and hands you a medal. You have done it. You commit your code and go to sleep. This high will help you fight your imposter syndrome for the next 3 days.

This post has become annoyingly self-aware.


  1. I realize that the given example flows pretty nicely. But in my actual use-case it felt out of place. ↩︎