6 JavaScript Features You Should Know

Published in WordPress.

Watch our video

There is a better version of your web

Share this post

A little over a year ago we talked about the 5 “new” JavaScript features you should know to develop comfortably in Gutenberg. There we learned what object and array destructuring are, how to create arrow functions, what the spread and rest operators are, and so on. Lucky you, JavaScript still has plenty of features that, once you’re familiar with them, will help you write more intelligible and succinct code.

Today I’ll show you six really cool JavaScript features. Operators, variable scoping, promises, asynchronous functions… are you ready to learn them all?

#1 Optional Chaining in JavaScript with the Operator ?.

The optional chaining operator allows simplified access to the attributes of an object when there’s a chance that one is undefined or null. For example, suppose you have an object like the following:

const toni = {
  name: 'Antonio',
  details: {
    age: 35,
  },
};

To access toni‘s age, you first have access the details attribute and then age:

function getAge( person ) {
  return person.details.age;
}

The problem we have in the previous function, and I’m sure it’s nothing new to you, is that the properties we expect the person to have aren’t always there. That is if either person or person.details is undefined, the above function will trigger a type error:

getAge( toni ); // 35
getAge( {} );   // Uncaught TypeError: person.details is undefined

Usually, we overcome this issue by adding a few safe guards:

function getAge( person ) {
  if ( ! person ) {
    return;
  }
  if ( ! person.details ) {
    return;
  }
  return person.details.age;
}

getAge( toni ); // 35
getAge( {} );   // undefined

which clearly fix it. Unfortunately, the resulting function is more complex and has a lot of noisy code that takes our attention away from things that really matter. To get back on track and fix the solution in a simpler way, all we have to do is use the optional chaining operator ?.:

function getAge( person ) {
  return person?.details?.age;
}

getAge( toni ); // 35
getAge( {} );   // undefined

Essentially, this operator let’s us access each property as long as it’s not undefined or null. As soon as it detects we try to access a property of a non-existing object, it returns undefined.

# 2 Nullish Coalescing Operator (??)

In JavaScript it is very easy to set a default value to a variable using this operator: ||, right? Wrong! We’ve all done this, but it can result in unexpected side effects if you’re not careful…

For instance, suppose we have a selector in our Redux store that allows us to retrieve a value set by the user. If the user hasn’t specified a value yet (that is, the value is undefined), we return a default value using ||:

function getValue( state ) {
  return state.value || 5;
}

Now, let’s see a few examples of what we’d get with the previous function after the user has set one value or another:

// No setValue( x )...
getValue(); // 5

setValue( 2 );
getValue(); // 2

setValue( 1 );
getValue(); // 1

setValue( 0 );
getValue(); // 5

Whoops! It all worked fine until we reached the last case! Apparently, if the user set the value to 0, the result of the function becomes 5. Why? Well, the rationale is pretty obious: x||y returns y if, and only if, x is a “falsey” value. This usually works fine, as undefined is a falsey value. But so are false or 0, for example.

In reality, we only wanted to set a default value when there’s none. So we can add a safe guard to check if there’s a value or not:

function getValue( state ) {
  if ( undefined === state.value ) return 5;
  return state.value;
}

and we can even wewrite it using the ternary operator if we’re feeling fancy:

function getValue( state ) {
  return undefined === state.value ? 5 : state.value;
}

but, once again, both solutions unnecessarily complicate the code. To assign a default value to a variable, we must use the nullish coalescing operator??, which returns the right part if, and only if, the left part is null or undefined:

function getValue( state ) {
  return state.value ?? 5;
}

and, surprise-surprise!, the result is exactly what we wanted:

// No setValue( x )...
getValue(); // 5

setValue( 2 );
getValue(); // 2

setValue( 1 );
getValue(); // 1

setValue( 0 );
getValue(); // 0

Oh, and, by the way, you can use this operator combined with an assignment. For instance, this:

value = value ?? 5;

is equivalent to this:

value ??= 5;

#3 Promises and asynchronous functions

Promises are a mechanism implemented in JavaScript that allows us to simplify our source code when working with asynchronous operations, that is, operations whose results aren’t immediately available. For example, if we want to retrieve data from our server, it is clear that the response will not be instantaneous, since we have to wait for the server to receive the request, process it, and send us the response back.

In older JavaScript versions and libraries (for example, jQuery) we used to implement this with callbacks. The idea was quite simple: along with the request itself, define the function (the callback) that should be called once the response is available. This way, when the asynchronous operation (i.e. retrieving data from the server) completes, the library will call the function and our logic will resume:

jQuery.ajax( {
  url: 'https://server.com/wp-json/wp/users/1',
  success: ( user ) => {
    console.log( user );
  },
} );

If we’re only triggering a single, isolated request, this solution is quite elegant and convenient. But, as soon as we need to perform more requests, things get dirty quickly. For example, if we want to request two different users, we’d have to nest callbacks:

jQuery.ajax( {
  url: 'https://server.com/wp-json/wp/v2/users/1',
  success: ( user1 ) => {
    jQuery.ajax( {
      url: 'https://server.com/wp-json/wp/v2/users/2',
      success: ( user2 ) => {
        console.log( user1, user2 );
      },
    } );
  },
} );

Promises are the solution to this issue: if obtaining a certain result is not immediate (such as, for example, when we retrieve something from a server), we can return a JavaScript promise right away. This object is a wrapper of the actual value and signifies the “promise” that said value will be available at some point in the future.

For example, if we were to rewrite our first snippet using promises, it’d look like this:

const promise = wp.apiFetch( {
  url: 'https://server.com/wp-json/wp/v2/users/1',
} );
promise.then( ( user ) => console.log( user ) );

As you can see, wp.apiFetch has to fetch user 1 from the server, but it gives a result right away. The result, however, is not the user themself, but a promise that will resolve to the user once the requests is completed. Therefore, all we have to do is write a callback that will process the promise’s response when it’s resolved.

You now might be thinking that this is not that difficult from what we had before, right? After all, we’re still using callbacks… but you can see how useful this is once we start to combine multiple requests:

const promise1 = wp.apiFetch( {
  url: 'https://server.com/wp-json/wp/v2/users/1',
} );
const promise2 = wp.apiFetch( {
  url: 'https://server.com/wp-json/wp/v2/users/2',
} );

Promise.all( [ promise1, promise2 ] ).then(
  ( [ user1, user2 ] ) => console.log( user1, user2 );
);

Using promises, we were able to launch two parallel requests to retrieve users 1 and 2 and use Promise.all to wait for both promises to resolve. No spaghetti code with nested callbacks involved.

Well, the great thing about promises is yet about to come. We can use some syntactic sugar to work with JavaScript promises and write asynchronous code that looks, well, synchronous. All you have to do is define an asynchronous function using the async keyword and suddenly things become way, way easier:

async function logTwoUsers( id1, id2 ) {
  const user1 = await wp.apiFetch( { url: '…' + id1 } );
  const user2 = await wp.apiFetch( { url: '…' + id2 } );
  console.log( user1, user2 );
}

Whenever you call an asynchronous operation within an asynchronous function, you can wait for its result using the await keyword. The only thing you should keep in mind is that, when you define an async function, its result will always be a promise:

async function getNumberFive() {
  return 5;
}

const p = getNumberFive(); // a promise
p.then( console.log );     // prints "5"

#4 Variable Scope when using let and const

As you probably already know, you can now declare variables using the let and const keywords. The former defines a variable and the latter defines a constant:

let x = 1;
console.log( x ); // 1
x = 2;
console.log( x ); // 2

const y = 1;
console.log( y ); // 1
y = 2;            // Uncaught TypeError: invalid assignment to const 'y'
console.log( y ); // 1

You might be tempted to think that let and var are the same thing, as both keywords allow us to declare a non-constant variable. But there is a substantial difference between them: their scope. With let and const, the scope of the variable is the block where it is defined. In var, it’s the whole function.

function fn() {
  if ( true ) {
    var x = 1;
    let y = 2;
    const z = 3;
  }//end if
  console.log( x ); // 1
  console.log( y ); // Uncaught ReferenceError: y is not defined
  console.log( z ); // Uncaught ReferenceError: z is not defined
}

# 5 Data Transformation when Using JSON.parse

The JSON.parse function parses a JSON string and builds a JavaScript object. For example:

const x = JSON.parse( '{"x":1,"a":[1,2,3]}' );
// Object { x: 1, a: [ 1, 2, 3 ] }

What most people don’t know about it is that it supports a second argument called reviver. This parameter is a function that will be executed for each element that is being parsed, allowing you to manipulate the value as you please. For example, imagine a JSON string like the following:

const json = '{"name":"David","birthday":"1985-12-01T10:00:00.000Z"}';

If we use JSON.parse as is, it will generate an object with two string attributes: name and birthday. But if we provide a reviver function to JSON.parse, we can make sure that birthday is parsed as a Date:

const user = JSON.parse(
  json,
  ( key, value ) => 'birthday' === key
    ? new Date( value )
    : value
)

# 6 Numeric Separator in Source Code Using the Undesrcore Character (_)

Our last tip today is the numeric separator. There is a proposal (currently in stage 4) that makes it possible to write numbers in a way that makes it easier for humans to understand. For instance, can you really tell how large the following numbers are?

10000000000
2189719.25

If only we could use the thousands separator, it would be much easier to interpret! And that’s precisely what we can do using the underscore _ :

10_000_000_000
2_189_719.25

Summary

It is possible to write better JavaScript code if you get used to all the new tools and features included in JavaScript. Today we have seen several examples of what is possible with this programming language. I hope you have learned something new and if you liked it, don’t forget to share it with your colleagues. See you soon!

Featured image by Sam Dan Truong on Unsplash.

Leave a Reply

Your email address will not be published. Required fields are marked: •

I have read and agree to the Nelio Software Privacy Policy

Your personal data will be located on SiteGround and will be treated by Nelio Software with the sole purpose of publishing this comment here. The legitimation is carried out through your express consent. Contact us to access, rectify, limit, or delete your data.