Tags

  • AWS (7)
  • Apigee (3)
  • ArchLinux (5)
  • Array (6)
  • Backtracking (6)
  • BinarySearch (6)
  • C++ (19)
  • CI&CD (3)
  • Calculus (2)
  • DesignPattern (43)
  • DisasterRecovery (1)
  • Docker (8)
  • DynamicProgramming (20)
  • FileSystem (11)
  • Frontend (2)
  • FunctionalProgramming (1)
  • GCP (1)
  • Gentoo (6)
  • Git (15)
  • Golang (1)
  • Graph (10)
  • GraphQL (1)
  • Hardware (1)
  • Hash (1)
  • Kafka (1)
  • LinkedList (13)
  • Linux (27)
  • Lodash (2)
  • MacOS (3)
  • Makefile (1)
  • Map (5)
  • MathHistory (1)
  • MySQL (21)
  • Neovim (10)
  • Network (66)
  • Nginx (6)
  • Node.js (33)
  • OpenGL (6)
  • PriorityQueue (1)
  • ProgrammingLanguage (9)
  • Python (10)
  • RealAnalysis (20)
  • Recursion (3)
  • Redis (1)
  • RegularExpression (1)
  • Ruby (19)
  • SQLite (1)
  • Sentry (3)
  • Set (4)
  • Shell (3)
  • SoftwareEngineering (12)
  • Sorting (2)
  • Stack (4)
  • String (2)
  • SystemDesign (13)
  • Terraform (2)
  • Tree (24)
  • Trie (2)
  • TwoPointers (16)
  • TypeScript (3)
  • Ubuntu (4)
  • Home

    JavaScript Proxy

    Published Dec 06, 2019 [  Node.js  ]

    A proxy is an object that controls access to another object, called a subject. The proxy and the subject have an identical interface and this allows us to transparently swap one for the other; in fact, the alternative name for this pattern is surrogate. A proxy intercepts all or some of the operations that are meant to be executed on the subject, augmenting or complementing their behavior.

    The Proxy and the subject have the same interface, and this is totally transparent to the client, who can use one or the other interchangeably. The Proxy forwards each operation to the subject, enhancing its behavior with additional pre-processing or post-processing.

    Notes

    It’s important to observe that we are not talking about proxying between classes; the proxy pattern involves wrapping actual instances of the subject, thus preserving its state.

    A proxy is useful in several circumstances; for example, consider the following ones:

    • Data Validation: The proxy validates the input before forwarding it to the subject
    • Security: The proxy verifies that the client is authorized to perform the operation and it passes the request to the subject only if the outcome of the check is positive
    • Caching: The proxy keeps an internal cache so that the operations are executed on the subject only if the data is not yet present in the cache
    • Lazy Initialization: If the creation of the subject is expensive, the proxy can delay it to when it’s really necessary
    • Logging: The proxy intercepts the method invocations and the relative parameters, recoding them as they happen
    • Remote Objects: A proxy can take an object that is located remotely, and make it appears local.

    Techniques for implementing proxies

    When proxying an object, we can decide to intercept all its methods or only some of them, while delegating the rest of them directly to the subject.

    Object composition

    Composition is a technique whereby an object is combined with another object for the purpose of extending or using its functionality. In the specific case of the proxy pattern, a new object with the same interface as the subject is created, and a reference to the subject is stored internally in the proxy in the form of an instance variable or a closure variable. The subject can be injected from the client at creation time or created by the proxy itself.

    function createProxy(subject){
        const proto = Object.getPrototypeOf(subject)
    
        function Proxy(subject){
            this.subject = subject
        }
    
        Proxy.prototype = Object.create(proto)
    
        // proxied method
        Proxy.prototype.hello = function(){
            return this.subject.hello() + ' world!';
        }
    
        // delegated method
        Proxy.prototype.goodbye = function(){
            return this.subject.goodbye.apply(this.subject, arguments)
        }
    
        return new Proxy(subject)
    }
    module.exports = createProxy
    
    function createProxy(subject){
        return {
            // proxied method
            hello: () => (subject.hello() + ' world!'),
    
            // delegated method
            goodbye: () => (subject.goodbye.apply(subject, arguments))
        }
    }
    

    Object augmentation

    Object augmentation (or monkey patching) is probably the most pragmatic way of proxying individual methods of an object and consists of modifying the subject directly by replacing a method with its proxied implementation

    function createProxy(subject) {
        const helloOrig = subject.hello;
        subject.hello = () => (helloOrig.call(this) + ' world!');
    
        return subject;
    }
    

    Creating a logging Writable stream

    We will now build an object that acts as a proxy to a Writable stream, by intercepting all the calls to the write() method and logging a message every time this happens.

    function createLoggingWritable(writableOrig){
        const proto = Object.getPrototypeOf(writableOrig)
    
        function LoggingWritable(writableOrig){
            this.writableOrig = writableOrig
        }
    
        LoggingWritable.prototype = Object.create(proto)
    
        LoggingWritable.prototype.write = function(chunk, encoding, callback){
            if(!callback && typeof encoding === 'function'){
                callback = encoding
                encoding = undefined
            }
            console.log('Writing ', chunk)
            return this.WritableOrig.write(chunk, encoding, function(){
                console.log('Finished writing ', chunk)
                callback && callback()
            })
        }
    
        LoggingWritable.prototype.on = function(){
            return this.writableOrig.on.apply(this.writableOrig, arguments)
        }
    
        LoggingWritable.prototype.on = function(){
            return this.writableOrig.end.apply(this.writableOrig, arguments)
        }
    
        return new LoggingWritable(writableOrig)
    }
    
    const fs = require('fs')
    
    const writable = fs.createWriteStream('test.txt')
    const writableProxy = createLoggingWritable(writable)
    writableProxy.write('First chunk')
    writableProxy.write('Second chunk')
    writable.write('This is not logged')
    writableProxy.end()
    

    ES2015 Proxy

    Example 1

    const proxy = new Proxy(target, handler)
    
    const scientist = {
        name: 'nikola',
        surname: 'tesla'
    }
    
    const uppercaseScientist = new Proxy(scientist, {
        get: (target, property) => target[property].toUpperCase()
    })
    
    consol.log(uppercaseScientist.name, uppercaseScientist.surname)
    // prints NIKOLA TESLA
    

    Example 2

    const evenNumbers = new Proxy([], {
        get: (target, index) => index * 2,
        has: (target, number) => number % 2 === 0
    })
    console.log(2 in evenNumbers)   // true
    console.log(5 in evenNumbers)   // false
    console.log(evenNumbers[7])     // 14
    

    References

    • Node.js Design Patterns