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 πŸ’š.

Latest comments (0)

11 Tips That Make You a Better Typescript Programmer

typescript

1 Think in {Set}

Type is an everyday concept to programmers, but it’s surprisingly difficult to define it succinctly. I find it helpful to use Set as a conceptual model instead.

#2 Understand declared type and narrowed type

One extremely powerful typescript feature is automatic type narrowing based on control flow. This means a variable has two types associated with it at any specific point of code location: a declaration type and a narrowed type.

#3 Use discriminated union instead of optional fields

...

Read the whole post now!