4

Fixing Null Pointer Exceptions with Proxies

 2 years ago
source link: https://dev.to/iostreamer/fixing-null-pointer-exceptions-with-proxies-19fc
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Rahul Ramteke

Posted on Apr 21

• Originally published at iostreamer.me on Jun 15, 2019

Fixing Null Pointer Exceptions with Proxies

No more Cannot set property of undefined


A proxy, as the name suggests, acts as an interceptor to an object. Every property one accesses, every property one sets, every operation, goes through the proxy.

Here are some articles to get you to speed on proxies:

Now that we have some idea of using proxies, we can try to solve this problem which came up while implementing a multitenant architecture I was working on.

Let’s say we have an empty object x = {} and for some reason we want to do x.y.z = 23; and not just x.y.z we want to do add keys and subkeys, infintely to x. So, x.y.z = 23 and x.y.z.a.i.v.b.n.s = 99 should not throw an error but rather should create the following objects respectively:

x = {
  y: {
    z: 23
  }
}

Enter fullscreen mode

Exit fullscreen mode

x = {
  y: {
    z: {
      a: {
        i: {
          v: {
            b: {
              n: {
                s: 99
              }
            }
          }
        }
      }
    }
  }
}

Enter fullscreen mode

Exit fullscreen mode

Problem is, if we were to do x.y.z = 23 on x = {}, we’d be trying to access property z assuming y exists on x, but actually it doesn’t, all we have in x is {}.

But what if our x was smart, so much so, that if we tried to access a property which doesn’t exist, it will create that property on the fly and our code won’t even know!

What if we intercepted every property access call on x, checked if the property actually exists, if it doesn’t, initialize it with empty object and then go about our usual business?!

So, doing x.y.z = 23 will first create y on x and then will set z

And this is where proxies shine, we could do this magic with a simple proxy like this:

function getUndefinedHandlerProxy() {
    const undefinedHandlerProxy = new Proxy({}, {
        get(target, key) {
            if (target[key] === undefined) {
                target[key] = {};
            }
            return target[key];
        },
    });
    return undefinedHandlerProxy;
}

Enter fullscreen mode

Exit fullscreen mode

The first argument to new Proxy($1, $2) is basically x, or the object we want a layer on, the object we want to intercept. The second argument is our handler, a collection of traps and interceptors.

Here, we decided to intercept property access calls using get trap. As we planned before, we will check if the property(or key in this case) exists on our object(called target here). This is done using the line if (target[key] === undefined) and if it doesn’t, we return an empty object, like we planned.

Let’s see how this helps us solve our problem:

const x = getUndefinedHandlerProxy();
x.y.z = 23;
console.log(x.y.z);

Enter fullscreen mode

Exit fullscreen mode

Our x is a proxy now, x.y.z was performed on our proxy, which caught us when we tried to access y. It checked if y existed, and since it did not, x.y returned an empty object, after which, z was simply set on that returned empty object!

One little problem though, what if we had x = getUndefinedHandlerProxy(); and did x.y.z.a = 78? Since x is proxy, y would be automatically created as a blank object. But since y is simple plebian, accessesing y.z.a would be problematic, as z doesn’t actually exist, so, we’d be setting aon undefined, and now we are back to our original problem. We need y to be as smart as x and not a simple object, same goes for all the sub properties that get created. What if y was actually a proxy, just like x?

We could do that by changing our proxy a bit, rather than doing target[key] = {}, we could dotarget[key] = getUndefinedHandlerProxy().

And now our final code would be:

function getUndefinedHandlerProxy() {
    const undefinedHandlerProxy = new Proxy({}, {
        get(target, key) {
            if (target[key] === undefined) {
                target[key] = getUndefinedHandlerProxy({});
            }
            return target[key];
        },
    });
    return undefinedHandlerProxy;
}

Enter fullscreen mode

Exit fullscreen mode

And this, handles everything.

One last caveat though, our proxy would always create an object when we try to access a property and it doesn’t exist. Now, what if we wanted to check if a property exists or not, which is a very common scenario in day to day javascript.

Doing:

if(ourSpecialProxy.a && ourSpecialProxy.a.x) {
  // Some special code
}

Enter fullscreen mode

Exit fullscreen mode

would not work as intended, the condition will always return true.

One way to avoid this, is not checking property existence directly, we need to check if the property is an actual object/string/number. Or if the property is anything but a proxy.

For this, we have:

function getActualValueFromUndefinedhandlerProxy(target, key) {
    if (util.types.isProxy(target[key])) {
        return undefined;
    }
    return target[key];
}

Enter fullscreen mode

Exit fullscreen mode

It’s a simple function which takes a target and key, if targey[key] is proxy, then the whole thing is a facade, it returns undefined. If not, then target[key] is returned.

And that’s it, we achieved what we set out to.

No more Cannot set property of undefined!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK