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() { ... },
}
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(' ... ')
+ }
...
},
}
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)
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)
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]
}
We moved every "if() { throw }
" block to one centralized place:
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. Don't want to dig deeper into this, maybe in another post.
- 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:
const a = { fn() { return 1 } }
const pa = new Proxy(a, {
get(target, prop) {
throw new Error(' ... ')
}
})
const { fn } = pa // π₯ Error
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
Banner image by Storyset
--
Thanks for reading π.
Top comments (0)