Advanced js: built-in Proxy use case
Tags: javascript, webdev, tutorial, node
Published: 2022-10-03 11:13:30

> **Using the right tool for solving a problem feels just great.** 

To start with:  we were exporting an object with some functions:

```js
export default obj = {
  foo() { ... },
  goo() { ... },
  hoo() { ... },
  ...
  zoo() { ... },
}
```

Then, we end up with this requirement: any method from `obj` should check an `isReady` condition; throw an exception if not fulfilled, keep the thing otherwise.

So, the PR was something like this:

```diff
+ let isReady = false // set true at some point
+
export default obj = {
  foo() { 
+  if (!isReady) {
+    throw new Error(' ... ')
+  }
  ...
  },

  goo() { 
+  if (!isReady) {
+    throw new Error(' ... ')
+  }
  ...
  },

  hoo() { 
+  if (!isReady) {
+    throw new Error(' ... ')
+  }
  ...
  },

  ...

  zoo() { 
+  if (!isReady) {
+    throw new Error(' ... ')
+  }
  ...
  },
}
```

_You get the idea_

At this point we were like "so sad we don't have a way to wrap the original object, and protect the access to its properties while keeping everything else..."

<img src="https://openmoji.org/data/color/svg/1F914.svg" width="300px" />

...Actually we **do** have a built-in object that solves the thing:

### [✨✨ Proxy ✨✨](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)

**Definition: **

> _"create an object that can be used in place of the original object, but which may redefine fundamental Object operations like getting, setting, and defining properties"_

So basically what we were looking for. **A bouncer**.

=> 

```js
const isReadyProxyHandler = {
  get(target, prop) {
    if (typeof target[prop] === 'function') {
      return (...args) => {
        if (!isReady) {
          throw new Error(' ... ')
        }
        return target[prop].apply(this, args)
      };
    } else {
      return target[prop]
    }
  },
};

export default new Proxy(obj, isReadyProxyHandler)
```

In detail, 

Instead of exporting the `obj` we're now exporting a Proxy wrapping up `obj`. We just need to define the proxy handler. 

```diff
- export default obj
+ export default new Proxy(obj, handler)
```

In this case, we trap the access (`get()`) and check if it's a function (maybe in another situation we'd need to check **any** property).

```js
  get(target, prop) {
    if (typeof target[prop] === 'function') {
      // function call
    } else {
      return target[prop]
    }
```

We moved every "`if() { throw }`" block to one centralized place: 

```js
get(target, prop) {
   
+   if (!isReady) {
+     throw new Error(' ... ')
+   }
+   return target[prop].apply(this, args)

}
```

***

Two things: 

- You can access the property directly (`return target[prop]`) or use another built-in object called [Reflect](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect). Don't want to dig deeper into this, maybe in another post.

```diff
- return target[prop]
+ return Reflect.get(...arguments)
```

- We are returning a function that may throw an exception. We can't raise the exception right away. 

Consider this:

```js
const a = { fn() { return 1 } }

const pa = new Proxy(a, { 
  get(target, prop) {  
    throw new Error(' ... ')
  }
})

const { fn } = pa // 💥 Error
```

While:

```js
const a = { fn() { return 1 } }

const pa = new Proxy(a, { 
  get(target, prop) {  
    return (args) => {
      throw new Error(' ... ')
    }
  }
})

const { fn } = pa // ok
fn() // 💥 BOOM
```

***

> Banner image <a href="https://storyset.com/online">by Storyset</a>

--
Thanks for reading 💚.