JavaScript Jems - Fluent Interfaces
Written by Mike James   
Wednesday, 15 February 2023
Article Index
JavaScript Jems - Fluent Interfaces
Singleton Chaining
Getting Complicated

Singleton Chaining

Before we get on to more complicated examples, let's examine another simple case - singleton chaining. A singleton is a JavaScript object that isn't created by a constructor - it is an object literal. In this case what we want to do is allow each function to be defined as a method of the same object, obj say.

To allow chaining we have to arrange for each method to return an object that each subsequent function is defined on and in this case it is just obj. It really is this simple. So let's define our object with three methods:

var obj = {
            function1: function () {
                alert("function1");
                return obj;
            },
            function2: function () {                 alert("function2");                 return obj;             },             function3: function () {                 alert("function3");                 return obj;             }         }

Notice that each function returns a reference to obj and this is what allows the next function call in the chain to work. With this definition you can write:

obj.function1().function2().function3();

and you will see the three alert boxes indicating that each function is called in turn.

Notice that this all depends on the variable obj not being reassigned between function calls. If you want to protect against this you need to create a private variable using closure that cannot be changed that the functions can return, or you can use a const if you are using ES2015.

Creating a private variable using closure is fairly easy:

var obj = function () {
            self = {
                function1: function () {
                                    alert("function1");
                                    return self;
                            },
                            function2: function () {
                                    alert("function2");
                                    return self;
                            },
                            function3: function () {
                                    alert("function3");
                                    return self;
                            }
            };
            return self;
        }();

Notice that what has happened is that we have wrapped the object definition in a function. This forces the self variable into the closure and all of the functions defined can access it, but no other part of the code can. Also notice that use of an immediate invocation of the function as explained at the end of the previous jem.

Instance Chaining

Singletons can be very useful, but often we want to be able to create as many instances of an object as desired and in this case we have to move on to look at instance chaining. This is very similar to the singleton case, but now we have to create a constructor that will produce an instance of the object.

var objConstruct = function(){
            this.function1= function () {
                alert("function1");                 return this;             };             this.function2=function () {                 alert("function2");                 return this;             };             this.function3= function () {                 alert("function3");                 return this;             }         };

Notice that we have defined three methods for the object we are constructing, but also notice the use of this to reference the object being constructed. What should each of the functions return to ensure that the next function can be called? In this case each function has to return this because, when the function is called, this references the instance that the function is a method of. Now we can write:

var obj = new objConstruct();
obj.function1().function2().function3();

and again we see the alert boxes announce that each function is called in turn.

If you can't see the difference between this and the singleton example, remember that we can now create as many instances of the object as we like. Also notice that because each function returns this as the object for the next function, all of the functions in the chain are called in the context of the same instance of the object.

Optional Chaining

ES2020 introduced the optional chaining operator ?. which can be used to handle potential errors in fluent calls. The idea is very simple:

object?.property

will return undefined if object is nullish, i.e. null or undefined, and the specified property otherwise. Notice that this is also a short circuit evaluation in that the property is not evaluated if object is nullish. Using optional chaining isn't difficult, but you do have to think about exactly what you are testing. For example:

A={};
A.method1=function(){console.log("method1");}; A?.method1();

works perfectly and you see method1 displayed as A is not nullish. However, if you try:

A?.method2();

you will see a runtime error because A is defined and hence an attempt is made to retrieve method2 and then call it. If method2 doesn't exist then a runtime error is generated. If method2 exists but isn't a function a runtime error is generated. To protect against method2 not being defined you need to use:

A?.method2?.();

or

A.method2?.();

Notice that you will still get a runtime error if method2 is defined but not a function. This is a slight extension of the optional chaining notation, but perfectly reasonable in that the invocation operator () is only evaluated if A.method2 is defined.

If you try:

let B; 
let C=B?.method2();

you will discover that C is undefined and no runtime error occurs. Notice however, if B is undeclared, i.e. no let B, then you will still get a runtime error.

The more general form:

object?.expression

can be used with any form of expression that makes sense in the context of accessing a property.

For example:

let result=myArray?.[42];

will return undefined if myArray is nullish and:

let result=myObject?.[propName];

will also return undefined if myArray is nullish.

Finally, it is worth noting that you cannot use optional chaining on the left-hand side of an assignment.



Last Updated ( Sunday, 16 July 2023 )