DEV Community

Cover image for Advanced js: built-in Proxy use case
Manuel Artero Anguita 🟨
Manuel Artero Anguita 🟨

Posted on

Advanced js: built-in Proxy use case

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

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

export default obj = {
  foo() { ... },
  goo() { ... },
  hoo() { ... },
  ...
  zoo() { ... },
}
Enter fullscreen mode Exit fullscreen mode

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:

+ 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(' ... ')
+  }
  ...
  },
}
Enter fullscreen mode Exit fullscreen mode

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..."

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

✨✨ 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.

=>

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)
Enter fullscreen mode Exit fullscreen mode

In detail,

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

- export default obj
+ export default new Proxy(obj, handler)
Enter fullscreen mode Exit fullscreen mode

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).

  get(target, prop) {
    if (typeof target[prop] === 'function') {
      // function call
    } else {
      return target[prop]
    }
Enter fullscreen mode Exit fullscreen mode

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

get(target, prop) {

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

}
Enter fullscreen mode Exit fullscreen mode

Two things:

  • You can access the property directly (return target[prop]) or use another built-in object called Reflect. Don't want to dig deeper into this, maybe in another post.
- return target[prop]
+ return Reflect.get(...arguments)
Enter fullscreen mode Exit fullscreen mode
  • We are returning a function that may throw an exception. We can't raise the exception right away.

Consider this:

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

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

const { fn } = pa // 💥 Error
Enter fullscreen mode Exit fullscreen mode

While:

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

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

const { fn } = pa // ok
fn() // 💥 BOOM
Enter fullscreen mode Exit fullscreen mode

Banner image by Storyset

--
Thanks for reading 💚.

Top comments (0)