I've been messing with the Proxy object lately, and I've created a very simple proxy, that allows for a neverending object chain. It's basically a chain of No operations, and yes I know it's a hard sell, but I used it to rekindle one of my old projects and it can work a (tiny) bit like Optional chaining (?.
), so I thought I'd share.
Implementation
It's a small proxy, with a [[ProxyHandler]] that traps [[Get]] and [[Call]], it simply returns the proxy object itself in each trap:
const neverendingObject = new Proxy(function () { }, {
apply: function () { return neverendingObject; },
get: function () { return neverendingObject; }
});
Note the [[ProxyTarget]] is a function, this will allow a neverendingObject()
function call without any extra chaining.
For educational purposes, I'll add logging in the traps, this way it's easier to get what's happening:
const neverendingObject = new Proxy(function () { }, {
apply: function (target) { console.log('[[Call]] ()'); return neverendingObject; },
get: function (target, prop) { console.log('[[Get]]', prop); return neverendingObject; }
});
neverendingObject.aProperty.aFunction().aProperty
// [[Get]] aProperty
// [[Get]] aFunction
// [[Call]] ()
// [[Get]] aProperty
How it works
I had some trouble wrapping my head around the apply
part, the extra get
call getting the reference to aFunction
first and then calling it after.
I've included the call chain below highlighting each step of the way, thinking of it that way helped me out (remember that each [[Get]] and [[Call]] will return the neverendingObject
object).
neverendingObject
[[Get]] aPropertyneverendingObject.aProperty
[[Get]] aFunctionneverendingObject.aProperty.aFunction
[[Call]] ()neverendingObject.aProperty.aFunction()
[[Get]] aPropertyneverendingObject.aProperty.aFunction().aProperty
Hopefully, this didn't add to any confusion.
The optional chaining (?.
) case
The neverendingObject
can not replace the ?.
operator, but it's possible to mimic the behavior of chaining without testing every step. but only if the last action is a function, and yes that's a show-stopper for most use cases.
const person = {
arms(hasNoArms) {
if (hasNoArms === true) {
return neverendingObject
}
return {
wave() { console.log('Waving arms'); }
};
}
}
person.arms().wave(); // "Waving arms"
person.arms(true).wave(); // Nothing happens
Using the ?.
operator, it would look like the following (assuming null
is returned instead of the neverendingObject
, when the hasNoArms
parameter is set):
person.arms(true)?.wave(); // Nothing happens
The ?.
operator is more explicit and would be the preferred way to go most of the time. As a bonus the ?.
operator stops execution of the rest of the chain, where the neverendingObject
will need to run the chain to its end, including any expensive calculation as an argument.
An actual use case
With everything, there's wrong about the neverendingObject
, I found a use case where it shines, which is adding conditional chaining to existing objects.
A very simple case is adding an and
function to the console
object, giving an easy way to only log output if a condition is true without the added if:
console.and = function (condition) {
if (condition) {
return console;
}
return neverendingObject;
}
console.and(1 == 1).log('is logged'); // "is logged"
console.and(1 == 2).log('is logged'); // Nothing happens
Conclusion
Let's compare a simple if
with the and()
function:
if (isDebugging) {
console.log('Some message');
}
// vs
console.and(isDebugging).log('some message');
It saves a couple of lines, and it's (subjectively) easier to read. When using the time()
and timeEnd()
to do some light profiling those couple of lines add up:
if (isDebugging) {
console.time('abcd');
}
// Expensive stuff
if (isDebugging) {
console.timeEnd('abcd');
}
// vs
console.and(isDebugging).time('abcd');
// Expensive stuff
console.and(isDebugging).timeEnd('abcd');
I've started a new branch of an old project: ConditionalConsole.
The previous version needed to create another object than console
. Using this technique I'm able to decorate it and make the functionality have a more native feel.
Top comments (0)