Saturday 13 June 2020

Lazy Objects via JavaScript Proxy

Related to my last post about Lazy promises it came to my mind the idea of creating a Generic Lazy object. A long while ago I had already tinkered with Lazy objects in JavaScript. At the end of that post I mention that sometime in the future I should try a different and much more transparent approach, using proxies, as I had done some weeks earlier in C#. Well, it has taken more than 7 years to turn that "in the future" into present... but finally, here it is.

The idea is pretty simple, we create a Proxy object with a get and set traps. The first time get or set are invoked, the lazy object has to be created. For that, the get and set trap functions keep as closure state the constructor function and the parameters to use in order to create the lazy object. When we create a Proxy we provide as first parameter the object that we are proxying. In this case such object does not exist, the proxy is on its own just to allow us to later create the object, so I was intending to pass null as parameter, but the compiler won't allow that, so I pass and "empty object": {}.
I'm thinking now that I could have used that object to hold the constructor and arguments, rather than keep them as closure variables... well, both approaches are equally valid.

I paste here the code, and you can also find it in this gist

 


//Create a Proxy for the lazy construction of an object
function buildLazyObject(constructorFn, ...args){
    let internalObject = null;
    //we don't care about the target, but compiler does not allow a null one, so let's pass an "empty object" {}
    return new Proxy({}, {
        get: function(target, property, receiver){
              internalObject = internalObject || (() => {
                console.log("Creating object");
                return new constructorFn(...args);
            })();
            
            //this way, if it's a method, internal calls to other methods of the class would go through the proxy also
            //for this case it makes no sense
            //return internalObject[property];
            
            let item = internalObject[property];
            if (typeof(item) === "function"){
                return function(...args){
                    return item.call(internalObject, ...args);
                };
            }
            else{
                return item;
            }
        },

        set: function(target, property, value, receiver){
            internalObject = internalObject || (() => {
                console.log("Creating object");
                return new constructorFn(...args);
            })();
            
            internalObject[property] = value;
        }
            
    });
}

//--------------------------------------------

class Person{
    constructor(name, age){
        this.name = name;
        this.age = age;
    }       

    sayHello(){
        return "Bonjour, Je suis " + this.name + " et j'ai " + this.age + ", ma ville est " + (this.city || "inconnu");
    }
}

let p1 = buildLazyObject(Person, "Francois", 2);
console.log("Proxy created");

console.log(p1.sayHello());

console.log("Second call");

console.log(p1.sayHello());

let p2 = buildLazyObject(Person, "Didier", 4);
console.log("Proxy created");
p2.city = "Marseille";
console.log("after set value");

console.log(p2.sayHello());
 
// Proxy created
// Creating object
// Bonjour, Je suis Francois et j'ai 2, ma ville est inconnu
// Second call
// Bonjour, Je suis Francois et j'ai 2, ma ville est inconnu
// Proxy created
// Creating object
// after set value
// Bonjour, Je suis Didier et j'ai 4, ma ville est Marseille



2 comments:

  1. great content for javascript proxy, if need best scraping proxy, can go to okeyproxy.com

    ReplyDelete
  2. I've learned so much from this content. It's truly helpful.free proxy trial

    ReplyDelete