First we should understand what proxies are actually. Using JavaScript proxies you can wrap an existing object and intercept access to its attributes or methods, even if they don't exist. You can also redefine fundamental operations for that object.
Syntax
There are 2 main terms in using Proxy.
Target - is the original object you want to wrap. It can be everything, also function.
Handler - is an object which define methods that intercept operations. These methods are also called "traps".
Basic Example
Here is the simplest example of Proxy. We have a dictionary with english and bulgarian translation of a few words. So what we want to do is get the bulgarian translation. We can see that everything is working as normal and when we want a word that is not included in the dictionary we get undefined.
const dictionary = {
apple: 'Ябълка',
peach: 'Праскова',
orange: 'Портокал'
};
const proxy = new Proxy(dictionary, {});
console.log(proxy['peach']); // Праскова
console.log(proxy['grapes']); // undefined
What about instead of this we want to return an appropriate message. More, we can add validations on setters. Here is an example:
const dictionary = {
apple: 'Ябълка',
peach: 'Праскова',
orange: 'Портокал'
};
const handler = {
get: (target, prop) => {
return target[prop] || 'The word is not translated yet'
},
set: (target, prop, val) => {
if (typeof val === 'string') {
target[prop] = val;
}
}
};
const proxy = new Proxy(dictionary, handler);
console.log(proxy['peach']); // Праскова
console.log(proxy['grapes']); // The word is not translated yet
proxy['grapes'] = 5;
console.log(proxy['grapes']); // The word is not translated yet
proxy['grapes'] = 'Грозде';
console.log(proxy['grapes']); //Грозде
Dynamic API Example
We saw a basic example of using Proxy and how to redefine operations. But let's see something a bit more interesting. Let's create an API that can intercept method calls that don't exist. For example we want to call an API with method that can include value or body represented as query and the proxy should return dynamically implement its functionality in runtime.
const {METHODS} = require('http');
const handler = {
get: (target, propName) => {
const method = METHODS.find(method => (propName.toLowerCase()).startsWith(method.toLowerCase()));
let propNameCopy = propName.slice(method.length + 1);
let index = propName.indexOf(propNameCopy.match(/[A-Z]/));
let path = index !== -1 ?
`/${propName.substring(method.length, index)}/${propName.substring(index)}`
:
`/${propName.substring(method.length)}`;
return (...args) => {
let queryOrBody = '';
if (args.length) {
const firstVal = args.shift();
typeof firstVal !== 'object' ? path += `/${firstVal}` : args.unshift(firstVal);
queryOrBody = args.shift() || {};
if (Object.keys(queryOrBody).length && method === 'GET') {
path += '?';
for (let key of Object.keys(queryOrBody)) {
path += `${key}=${queryOrBody[key]}&`;
}
path = path.slice(0, -1);
}
}
console.log(`Method: ${method} Path: ${path.toLowerCase()} ${method === 'POST' ? ('Body:' + JSON.stringify(queryOrBody)) : ''}`);
}
}
};
const api = new Proxy({}, handler);
api.getUsers(); // Method: GET Path: /users
api.getUserLikes('98'); // Method: GET Path: /user/likes/98
api.getUserPosts('98', {page: 2, name: 'JavaScript'}); // Method: GET Path: /user/posts/98?page=2&name=javascript
api.postArticle({name: 'Title', description: 'Some text'}); // Method: POST Path: /article Body:{"name":"Title","description":"Some text"}
Let me explain what the code is doing. First we are getting the type of the method which can be GET, POST, PATCH, DELETE. Then we want to get the endpoint of the method. After that we check if the method is GET and have query parameters, we add them to the endpoint if not, we check if there is body of the request. Finally we display the API call result.
To summarize a proxy can be used wherever an object is expected, and the complex functionality that proxies provide with just a few lines of code makes it an ideal feature for metaprogramming.
Top comments (0)