I play around a lot with javascript's new features and I try always to find a real use case for them. Recently I was a big fan with Javascript Proxies and what facinates me is the ability to intercept access to an object properties and change its behaviour.
Update: I created an egghead.io lesson about intercepting property access with Javascript Proxy, feel free to check it out.
A Javascript Proxy is a very interesting es6 feature, that allows you to determine behaviours whenever a property is accessed in a target object
var p = new Proxy(target, handler)
handler: An object whose properties are functions which define the behavior of the javascript proxy when an operation is performed on it.
target: can be any sorts of objects, or another javascript proxy
You define traps
for each operation you want to intercept and define the behaviour. You can have traps for:
get
set
getPrototypeOf
setPrototypeOf
isExtensible
preventExtensions
getOwnPropertyDescriptor
defineProperty
has
deleteProperty
ownKeys
apply
construct
A trap is basically a function ¯_(ツ)_/¯.
It is optional, if you don’t define them the operation gets forwarded to the target aka No-op forwarding proxy
What if you intercept your users access to an object and make sure they are doing the right thing, make your library a little bit more mistake friendly or give better direction on how your library should be used. Also, what if you want to change the structure of your data and you don't want to break other services that consumes that. In this blog post I'll try to cover two scenarios where you can use the javascript Proxies, data migration and build a mistake friendly library.
Data structures are hard to get right from the first time so throughout the lifecycle of your application you might want to change the data structure like removing uncessary properties and or group few properties together and the list goes on an on..., I myself been through that a lot of times. Depends how your app is structured you might have a service as a data provider and multiple services that act like data consumers. Once the app grows a lot changing data structure may be scary and error prone or maybe you don't have the capacity to refactor all of your services. Proxies will make sure that your data consumers will always get what they are asking for until you have time to refactor them and once all your code is ready for the change you can pull out the Proxy object.
We start first with our data service, the one that provides data to other services, we'll name it ScheherazadeDB
that exposes users data in a "super secure way". As first iteration the user data structure will look like this.
const user = {
username: 'alibaba',
password: 'open sesame',
address: 'Far far away'
firsName: 'Ali',
lastName: 'Baba',
age: '44'
}
Now in our app we have ScheherazadeDB
consumers especially Shahryar
, and to make our situation even harder this will run every night for the next one thousand and one nights until dawn consuming all the data and if the data provider, ScheherazadeDB
, fail to deliver the expected data the consumer will decide that the app does not worth it anymore and will kill the app and all its processes.
The first few nights went really good until someone in the team decided that the user data structure is too flat and we should do some grouping, like having the firstName, lastName and age in side of personalInfos
group. How can we make the change without breaking running consumers?
Javascript Proxy Object to the rescue. Let's start with the new data structure that we need.
const user = {
username: 'alibaba',
password: 'open sesame',
address: 'Far far away'
personalInfos: {
firsName: 'Ali',
lastName: 'Baba',
age: '44'
}
}
The problem here is that none of the consumers knows about the new structure and they will try to get or set properties that does not exist anymore like firstName and our app will die, let's fix this before our evil consumer start running again.
Using the javascript Proxy Object we can redirect get and set to the right property so if a consumer is requesting user.firstName
we can return user.personalInfos.firstName
.
Our javascript proxy Object will look like this
function proxyUser (user) {
const handler = {
get: (target, prop) => {
// if the key is groupped we redirect it to there
if (prop in target.personalInfos) {
return target.personalInfos[prop]
}
// default access
return target[prop]
},
set: (target, prop, value) => {
// we do the same for set
// but here we need to return because we don't want to set
// the value twice in the group and in the object
if (prop in target.personalInfos) {
target.personalInfos[prop] = value
// on a set trap we need to return true to mark the set as successful
return true
}
target[prop] = value
return true
}
}
const proxy = new Proxy(user, handler)
return proxy
}
the only thing we need to do now is to change our cheherazadeDB
service to give back the proxyUser instead of the original user object.
export async function getUser (id) {
const user = await selectSingleUser({where: {$id: id}})
// --- previously ---
//return user
//--- now ---
const proxiedUser = proxyUser(user)
return proxiedUser
}
Great, now that our services are running fine we need to warn the developer that some fields are deprecated and they should use the new properties. Let's change our proxyUser
function to reflect that.
function proxyUser (user) {
const handler = {
get: (target, prop) => {
// if the key is groupped we redirect it to there
if (prop in target.personalInfos) {
// NEW: Add deprecation warning
console.warn(`Accessing ${prop} directly will be deprecated in the next version please use myObject.personalInfos.${key} instead`)
return target.personalInfos[prop]
}
// default access
return target[prop]
},
set: (target, prop, value) => {
// we do the same for set
// but here we need to return because we don't want to set
// the value twice in the group and in the object
if (prop in target.personalInfos) {
// NEW: Add deprecation warning
console.warn(`Accessing ${prop} directly will be deprecated in the next version please use myObject.personalInfos.${key} = value instead`)
target.personalInfos[prop] = value
// on a set trap we need to return true to mark the set as successful
return true
}
target[prop] = value
return true
}
}
const proxy = new Proxy(user, handler)
return proxy
Now whenever a service tries to use the data will receive that warning for example
//....
const user = await getUser('<id>')
console.log(user.firstName)
//...
we'll get this output in the console.
And so the service consumer kept
ScheherazadeDB
alive day by day, as he eagerly anticipated the finishing of the previous night's story. At the end of 1,001 nights, and 1,000 stories, Scheherazade told the king that she had no more tales to tell him. During these 1,001 nights, the king had fallen in love with Scheherazade. He spared her life, and made her his queen.
Yes since most of the major browsers supports it already.
Same for Nodejs, since v6.9 javascript Proxy object is fully supported
With the javascript proxy object possibility are endless, you can resitrict the write access to some properties, logging access and so on. There is a really nice in depth article by ponyfoo you should check it out. Also I had a presentation about Proxies you can find it here
Enjoyed the content? Receive the next one in your inbox! No spam, just content.
Khaled Garbaya is a software developer and active opensourcerer at Contentful. He speaks multiple languages and is often overheard saying "Bonjour" in HTML. You can follow him on Twitter, on Github, and on YouTube. He also runs How To Contentful as a project.