The community is working on translating this tutorial into Uzbek, but it seems that no one has started the translation process for this article yet. If you can help us, then please click "More info".
Optional chaining operator
In this article, I'll show you a pretty cool operator called the optional chaining operator. But to understand why it's so useful, allow me to give you a little bit of background information first.
Unlike some other programming languages, JavaScript is quite forgiving if you try to access a member of an object which hasn't been defined. For instance, the following example won't result in any errors:
let user = { };
alert(user.name);
As you can see, I create a new user object, with no properties, and then I try to access a property called "name". Obviously this property doesn't exist, but instead of throwing an error and/or causing the execution to halt, JavaScript will simply return "undefined", which is also what the alert() will say.
However, JavaScript won't be quite as forgiving if you try to access an object that hasn't been defined. So, if we were to leave out the first line of the example above, and try to access the user object, which now obviously doesn't exist, an error will be thrown:
// Error: Uncaught ReferenceError: user is not defined
alert(user);
As you can see, an error is thrown and the execution of the script is halted. Now obviously this situation, where you try to access an object that hasn't been defined, shouldn't happen a lot, and if it does, it's clearly an error that should be corrected.
It becomes less clear what has been defined and what hasn't been defined when working with complex, nested objects, especially if you receive these from an external source, e.g. as JSON objects (more on those later). So, consider the following example, where we have extended our user object a bit:
let user =
{
name:
{
firstName: "John",
lastName: "Doe"
}
};
alert(user.name.firstName);
This example will work just fine - we only access properties which have been defined. The name property of the user object is now an object as well, consisting of two properties, firstName and lastName.
However, this also means that if we were to try and access these nested properties (firstName/lastName), without the parent object (name) being defined, JavaScript would not be as forgiving as we saw in the first example:
let user =
{
/*name:
{
firstName: "John",
lastName: "Doe"
}*/
};
// undefined (no error)
alert(user.name);
// Error: Uncaught TypeError: Cannot read properties of undefined (reading 'firstName')
alert(user.name.firstName);
Notice how the first alert() won't cause an error, but the second one does - we can access a property that has not been defined (name), but we can't access a property (firstName) of an object that hasn't been defined (name).
Using the optional chaining operator
With that in mind, we have finally arrived at the purpose of this article. Because sometimes, you don't want your code to throw errors if you try to access various, nested properties. The reason will often be that you're working with complex objects which you receive from an external source, which may or may not have the properties you would expect. This is where we can use the optional chaining operator:
let user =
{
/*name:
{
firstName: "John",
lastName: "Doe"
}*/
};
// undefined (no error)
alert(user.name);
// undefined (no error)
alert(user.name?.firstName);
Notice the very subtle difference in this example, to be found on the very last line. I have added a question-mark after the name property, which, in this context, will act as a conditional chaining operator (the chain being the nested properties).
With this operator in place, I'm basically telling JavaScript "Hey, I would like to access the firstName property on the name object, but I'm not quite sure that the name object has been defined, so in case it hasn't, I'm willing to accept "undefined" as a result - you don't need to throw any errors!".
For multiple parts
And you can use it for as many parts of the "chain" as needed, like this:
let user =
{
};
// undefined (no error)
alert(user.name?.firstName?.firstChar?.secondChar);
For methods
When trying to call a method on an object, the method has to be defined - otherwise, an error will be thrown and execution will be stopped:
let user =
{
};
// Error: Uncaught TypeError: user.getName is not a function"
alert(user.getName());
But if we don't want this behavior, we can use the optional chaining operator, because it works for methods as well. Here's an example:
let user =
{
};
// undefined (no error)
alert(user.getName?.());
It also works if you expect this method to return an object, on which you would like to access a property:
let user =
{
};
// undefined (no error)
alert(user.getName?.().firstName);
Summary
When working with objects, especially complex, nested objects, and especially if they come from external sources, the optional chaining operator is very useful to prevent unexpected errors. It will allow you to try to access undefined objects/properties/methods, without causing any execution stops in your code.