The Optional Chaining proposal is awesome and will definitely improve the JS ecosystem with the features it includes! More at tc39.
Here's a simple tutorial to learn about it by helping an adventurer to not break our code βοΈ
We'll create an Adventurer Class with some incomplete methods that access properties that may not be defined.
class Adventurer {
constructor({ name, health, skills, weapon, pet }) {
this.name = name;
this.health = health;
this.skills = skills;
this.weapon = weapon;
this.pet = pet;
}
attackWithWeapon() {
return this.weapon.damage;
}
attackWithPet() {
return this.pet.attack();
}
}
It's time now to instance the Adventurer
class and create a new adventurer called John.
const adventurer = new Adventurer({
name: 'John',
health: 100,
skills: {
run: {
description: 'Escapes the battle as fast as possible! π'
},
negotiate: {
description: 'Talks to the enemy until finding an opportunity to run away π'
}
}
});
He is not the bravest one in the party, but we love him anyway.
We could try to get the adventurer's health points with a getHealth
function but it's not defined!
This means doing adventurer.getHealth()
will throw an error β
Calling getHealth
direclty breaks code, by adding the &&
operator we can check if it exists before invoking.
console.log(adventurer.getHealth && adventurer.getHealth());
Let's add the new Optional Chaining ββ
console.log(adventurer.getHealth?.()); // The optional function invokation shortcircuits and return `undefined`. Much cleaner π!
And a fallback value now so we never return undefined
console.log(adventurer.getHealth?.() || adventurer.health); // This will fallback to adventurer.health which has a value of 100.
We want our adventurer to attack with the weapon, but we forgot to create one! This means accesing weapon.damage
will break the code again! π±
adventurer.attackWithWeapon()
should be improved... Optional Chaining can rescue us in this situations!
We'll create a new attackWithWeapon
function that uses Optional Chaining and rewrite the instance method with our improved version.
function attackWithWeapon() {
return this.weapon?.damage;
}
adventurer.attackWithWeapon = attackWithWeapon;
And call it to see if we fixed the issue. Our code shouldn't break now...
console.log(adventurer.attackWithWeapon()); // Returns undefined instead of breaking!
We could also add a fallback value so we never return undefined
console.log(adventurer.attackWithWeapon() || 1); // Our adventurer isn't really strong, but doing our best is always the best option
We'll now order our pet to attack. This is kinda RPG so our pets are invulnerable super strong llamas or something similar π¦
We'll send our pet to attack the enemy, but wait... where's our pet?! π
We forgot to give one to the adventurer! That means adventurer.attackWithPet
will break our code again...
Let's do the same as before and fix the method π‘
function attackWithPet() {
return this.pet?.attack?.() || 0;
}
adventurer.attackWithPet = attackWithPet;
We did a double check up there! βοΈ First we are checking if a pet exists and try to access attack, after that, we check if attack is defined and invoke it. We even added a fallback value of 0 because having no pet means we can't even do 1 damage.
console.log(adventurer.attackWithPet()); // Returns 0 instead of breaking!
We finally fixed our adventurer! π
Last but not least, did you know we can also access dynamic properties this way? We could map all the skills available for adventurers so we have an array with all our adventurer's skills descriptions βοΈ
const allSkills = [
'run',
'negotiate',
'intimidate',
'scare',
'menace',
];
const adventurerSkillsDescriptions = allSkills
.map(skillName => {
// Accesing only skills that exist in adventurer.skills before accessing the description.
const skillDescrition = adventurer.skills?.[skillName]?.description;
return skillDescrition;
})
.filter(Boolean); // Filtering the undefined values before assigning the array.
And here we have an array with all the descriptions! π
console.log(adventurerSkillsDescriptions);
We are done! We learnt something new today and helped our adventurer survive in the buggy world of code! π
PS: We should definitely teach the dev who made the Adventurer class our new skills so we don't have these issues anymore π
This was made with Parcel
by adding the @babel/plugin-proposal-optional-chaining
plugin to .babelrc
.
Just run npm start
and open the console in your browser to see the logs!