Skip to content

Latest commit

 

History

History
255 lines (194 loc) · 8.37 KB

v8-v8.md

File metadata and controls

255 lines (194 loc) · 8.37 KB
Error in user YAML: (<unknown>): did not find expected key while parsing a block mapping at line 1 column 1
---
layout: page-fullwidth
title:  "V8's V8: Optional Chaining and Nullish Coalescing"
,
categories:
    - JS
tags:
    - bacon
header: no
breadcrumb: true
meta_description: "An overview of how to use V8's optional chaining and nullish coalescing feature"
author: Jack Misteli
---

I think the V8 team at Google was waiting for this moment for a long time. The 8th version of V8 (the most popular JavaScript engine) is out! This new version comes with some really nice performance improvements and two new cool JavaScript language features: optional chaining and nullish coalescing.

Optional Chaining

This is a long-awaited feature. Let's say you are working with a terrible API provider. Let's call this API CrocosAPI. CrocosAPI provides information about all the crocodiles in the world (it is an unstable API every one knows 'gators are superior to crocs').

This is what our function to get a croc's habitat (some crocodiles live in freshwater, brakishwater and/or saltwater)

const getWaterHabitat = async (crocName) => {
  // First we call our made up api
  const crocResponse = await fetch('http://api.crocosapi.io/croc/' + crocName);
  // We get the responses body
  const crocInfo = crocResponse.body;
  // We access the water property of the habitat property
  return crocInfo.habitat.water;
}

// Let's get the water habitat of a crocodile called Barry
const barrysWaterHabitat = getWaterHabitat('Barry')
// this returnsbarrysWaterHabitat == ['freshwater']

Now let's say the developers of CrocosAPI decided to change the structure of their response from :

{
  "personalInfo" : {
    "firstName" : "Barry"
  },
  "habitat": {
    "water": ["freshwater"],
  }
  // ... skipping
}

To:

{
  "personalInfo" : {
    "firstName" : "Barry"
    //  ...
  },
  // We changed 'habitat' to 'environment'
  "environment": {
    "water": ["freshwater"]
  }
  //...
}

Now if we call getWaterHabitat we will get:

TypeError: Cannot read property 'water' of undefined

That's because crocInfo.habitat doesn't exist anymore. Now to access crocInfo, we have to access crocInfo.environment.water. This means our entire app will crash just because CrocosAPI's developers don't know about versioning. So how to avoid that error? Optional chaining of course!

const getWaterHabitat = async (crocName) => {
  const crocResponse = await fetch('http://api.crocosapi.io/croc/' + crocName)
  const crocInfo = crocResponse.body
  // We access the water key with optional chaining
  return crocInfo?.habitat?.water
}

const myCrocsName = 'Barry'
const barrysWaterHabitat = getWaterHabitat('Barry')
// barrysWaterHabitat == undefined

You can also use optional indexing with arrays:

const crocAddress1City = crocInfo?.personalInfo?.addresses?.[0].city
// if  crocInfo.personalInfo.addresses = []
// crocAddress1City === undefined

... And with functions!

// It is hard to make a short example
const getSkinStyle = (option) => {
  const scales = {
    color: 'green',
    texture: 'shiny'
  }
  if (option == 'naked')
    return
  else 
    return scales
}

const crocInfo = {
  name: 'Barry', 
  waterHabitat : 'Freshwaters',
  getSkinStyle : getSkinStyle
}

const barrysSkinColor = crocInfo?.getSkinStyle?.('naked')?.color
// barrysSkinColor === undefined
const larrysSkinColor = crocInfo?.getSkinStyle?.('naked')?.color
// larrysSkinColor === 'green'

... And with dynamic property access. Wow, it really is holiday season ⛄🎄🎁 (at the time of the writing)!

// habitatType can be "water" or "land"
const getHabitatProperty = (habitatType) => {
  return crocInfo?.habitat?.[habitatType]
}
getHabitatType('water')
// returns  ['freshwater']
getHabitatType('land')
// returns ['forest']

No more type errors anymore, just an undefined value!

As a quick PSA don't rely on optional chaining as an excuse not to do proper error handling. The good thing about the TypeError we get from access the property of an undefined value is that:

  • It is easier to notice unexpected behavior in our applications
  • It forces us to write better fallback mechanisms

We should still have some sort of fallback or warning mechanism when trying to access the property of an undefined value.

Nullish coalescing

?? ... No I'm not confused ?? is the new short-circuit operator joining the && and || family. If you wrote some React, Vue or Angular you have probably already written or seen something like this.

const name = props.name || 'CrocName Error';
const age = props.age || 'Invalid Age';
const isFemale = props.isFemale || true;
// pass name , age and isFemale to a view

This code will assign the value stored in props.name if it is not falsy. If the value is falsy, the value name will equal CrocName Error.

But let's say that for crocodiles who still haven't been named, the API returns an empty string. In JavaScript, an empty string is considered falsy so this will happen:

// Let's say we have an anonymous new born boy crocodile
const props  = {
  name: '',
  age: 0,
  isFemale: false
}

const name = props.name || 'CrocName Error';
// name === 'CrocName Error'

const age = props.age || 'Invalid Age';
// age === 'Invalid Age'

const isFemale = props.isFemale || true;
// isFemale === true

These are not the results we were expecting! We want to separate the scenario where props.name has a null or undefined value to the case where props.name is an empty string. We want age to equal 0 and isFemale to be false. That's where ?? comes to the rescue.

const name = '' ?? 'CrocName Error'
// name === '' 

const age = 0 ?? 'Invalid Age';
// age === 0

const isFemale = false ?? true;
// isFemale === false

// Yay it worked!

|| checks if the left hand side operator is falsy. ?? only checks if it is null or undefined. Here is a little cheat sheet:

// +0, -0, NaN, false, empty strings, null and undefined are all falsy
false ?? true;   // equals false
false || true;   // equals true

0 ?? 1;          // equals 0
0 || 1;          // equals `

'' ?? 'default'; // equals ''
'' || 'default'; // equals ''

// null and undefined are falsy so in this case we  get the same results
null ?? [];      // equals []
null || [];      // equals []

undefined ?? []; // equals []
undefined || []; // equals []

You can also mix operators! Just remember to use parenthesis. Try to think about what this would do:

const crocName = (props.name ?? 'CrocName Error') || 'Anonymous Croc';

Let's look at the result of a few values:

  • props.name === 'Barry': crocName === 'Barry'
  • props.name === '' : crocName ==== 'Anonymous Croc'
  • props.name === undefined : CrocName Error'

Nullish coalescing and optional chaining working together

You might have thought of cool ways to use these two features together!

const getCrocName = async (crocId) => {
  // We try to access our unstable API's data
  const crocResponse = await fetch('http://api.crocosapi.io/croc/' + crocId)
  // If croc response or body is undefined 
  const crocInfo = crocResponse?.body ?? 'Croc API did not return a valid response'
  // if crocResponse equals {} then crocInfo == 'Croc API did not return a valid response'

  // checking if crocInfo, personal info or name is undefined/null or if name has a falsy value
  return (crocInfo?.personalInfo?.name ?? 'There was a problem loading the croc\'s name') || 'Anonymous Croc'
  // if crocInfo?.personalInfo?.name === '' we return 'Anonymous Croc'
  //  if crocInfo equals {} we return 'There was a problem loading the croc\'s name'
}

V8 V8's Performance

Nowadays we are spoiled with how fast JavaScript is and even more spoiled by recurrent performance updates. Once again v8's engineers improved the performance and memory of their engine. If you're interested in learning more about how you can check out their release post. The thing I love about these updates is that it improves our code performance, but we don't have to write anything new!

Tiny Bonus

To check if you can use v8 v8 in node you can run node -p process.versions.v8 and see if the version is over 8. For now you should use polyfills like core-js on the web and/or a transpiler. If you are using Babel @babel/plugin-proposal-optional-chaining, @babel/plugin-proposal-nullish-coalescing-operator are available.

Have fun and happy holidays!