Marcell Ciszek Druzynski

Proxy pattern

The Proxy pattern in javascript provides a wrapper over another object that decides/controls the access to the original object. The proxy pattern can be used to implement a variety of features, such as data validation, lazy loading, or implementing a virtual object. It is a powerful tool for customizing the behavior of objects in JavaScript

February 21, 2023

the proxy pattern provides a wrapper over another object that decides/controls the access to the original object.

For example, allowing us to do something before or after the request get’s through the main/original object.

The proxy pattern is in between the client on the left side and the main object on the right side, like a wall where the client's requests need to go through the proxy and be handled before we reach the main object.

The clients do not have to know that they are talking to a proxy since the proxy and the main object implant the same interface/contract.

Why use a proxy

  • Handle logic before or after - If we need to execute something before or after the main logic, a proxy lets us do that without changing the original object.
  • Validation - To gain control of the original object and not expect any strange behaviors like for example mutations, we can use a proxy to control what is allowed or not.
  • controls HTTP requests - HTTP proxy’s to restrict HTTP requests, tools like Nginx are popular.
  • logging —> Logging property access.

Proxy example

Let’s say we have a dog object, where users can access its properties, modify them, or in a similar fashion.

When using the Proxy pattern, the Proxy will handle the requests and not the main object, we do not want to allow to directly talk to our main dog object. The Proxy takes in the requests and forwards them to the target(dog object).

With the Proxy pattern, we don't want to interact with this object directly. Instead, a Proxy object intercepts the request, and (optionally) forwards this to the target object - the person object in this case.

Javascript handles this in a nice way since ES6 was introduced, we can use the built-in Proxy API, to wrap our main dog object in this case. Let’s see what this would look like.

app/main.ts
1const snickers = {
2 name: "Snickers",
3 age: 2,
4 breed: "King Charles",
5 owner: "Marcell Ciszek",
6};
7
8const handler = {
9 get(
10 target: Record<string, string | number>,
11 prop: string,
12 receiver: any,
13 ): string | number {
14 return Reflect.get(target, prop, receiver);
15 },
16 set(
17 target: Record<string, string | number>,
18 prop: string,
19 value: string | number,
20 ): boolean {
21 return Reflect.set(target, prop, value);
22 },
23};
24const dogProxy = new Proxy(snickers, handler);
app/main.ts
1const snickers = {
2 name: "Snickers",
3 age: 2,
4 breed: "King Charles",
5 owner: "Marcell Ciszek",
6};
7
8const handler = {
9 get(
10 target: Record<string, string | number>,
11 prop: string,
12 receiver: any,
13 ): string | number {
14 return Reflect.get(target, prop, receiver);
15 },
16 set(
17 target: Record<string, string | number>,
18 prop: string,
19 value: string | number,
20 ): boolean {
21 return Reflect.set(target, prop, value);
22 },
23};
24const dogProxy = new Proxy(snickers, handler);

Here we are using the built-in Reflect object that makes it easier to work with our Proxy, where the Reflect object provides us useful methods that we can use when we getting or setting new values on our main object through our Proxy. Instead of getting a property from our object like obj[prop] or setting a new value like obj[prop] = value. We can access and set new values from the Reflect object, Reflect.get() or Reflect.set(). The methods receive the same arguments as the methods on the handler object.

From MDN

The Proxy object enables you to create a proxy for another object, which can intercept and redefine fundamental operations for that object.

Creating enums using a proxy

The Proxy object can be useful in scenarios where we need to create an immutable enum that allows only read-only access for the user. You can leverage this feature to create your own utility function in JavaScript for enum creation.

app/main.ts
1const Enum = (base) => {
2 return new Proxy(base, {
3 // focus(1:10)
4 get(target, key) {
5 if (!base.hasOwnProperty(key)) {
6 throw new Error(`No such key ${key}`);
7 }
8 return Reflect.get(target, key);
9 },
10 set(target, key, value) {
11 throw new Error(`Cannot set ${key} on ${target}`);
12 },
13 });
14};
15
16const Days = Enum({
17 Monday: 1,
18 Tuesday: 2,
19 Wednesday: 3,
20 Thursday: 4,
21 Friday: 5,
22});
23
24const getDay = (day) => {
25 try {
26 return Days[day];
27 } catch (error) {
28 return error.message;
29 }
30};
31
32const monday = getDay("Monday");
33console.log(monday);
34const aDayThatDoesNotExist = getDay("Fooo");
35console.log(aDayThatDoesNotExist);
app/main.ts
1const Enum = (base) => {
2 return new Proxy(base, {
3 // focus(1:10)
4 get(target, key) {
5 if (!base.hasOwnProperty(key)) {
6 throw new Error(`No such key ${key}`);
7 }
8 return Reflect.get(target, key);
9 },
10 set(target, key, value) {
11 throw new Error(`Cannot set ${key} on ${target}`);
12 },
13 });
14};
15
16const Days = Enum({
17 Monday: 1,
18 Tuesday: 2,
19 Wednesday: 3,
20 Thursday: 4,
21 Friday: 5,
22});
23
24const getDay = (day) => {
25 try {
26 return Days[day];
27 } catch (error) {
28 return error.message;
29 }
30};
31
32const monday = getDay("Monday");
33console.log(monday);
34const aDayThatDoesNotExist = getDay("Fooo");
35console.log(aDayThatDoesNotExist);

We are restricted to accessing only the keys that are present in the Enum. If we attempt to access a key that does not exist, an error is thrown. However, we can handle this error by using a function that catches it and displays a user-friendly message instead.

Pros and cons

Pros:

  • We gain control by accessing our main object. Adding useful functionality like logging, validation, notification or debugging makes it easier when using a Proxy.
  • When using a proxy (proxy servers) we can track users' access to the network. We can block websites that we think it is not suitable for our users. If you want to control for some reason what your employees are doing throughout the day or track every single request that is made to a special service.

Cons: _ We store more in memory which can lead to performance issues. _ Intruding more complexity to our codebase. * The response might get delayed.

Summary

The Proxy object is used to create a custom behavior for a target object (e.g. property lookup, assignment, enumeration, function invocation, etc).

A proxy is an object that stays in between another object, where it controls its behavior. It can be used to use and modify operations on the target object, including getting and setting properties, and calling methods.

References