5 Javascript Features That You Should Learn and Use Every Day


Starting from ES6 we got destructuring feature. It helps us to simplify our code in getting nested parameters

Let's say that we have a function which returns a property

const foo = (data) => {
  return data.items;

We can use destructuring here to create directly a local property items and return it.

const foo = ({ items }) => {
  return items;

This is just a sugar to

const foo = (data) => {
  const items = items.data;
  return items;

We can do the same with objects.

const data = {
  items: [],
  isLoading: true
console.log(data.items, data.isLoading);

If we don't use data a lot it makes sense to use destructuring here.

const { items, isLoading } = {
  items: [],
  isLoading: true,
console.log(items, isLoading);

In this case we avoid creation of unnecessary variable.

And it is extremely useful in module imports.

const fs = require("fs");
const { move, rename } = require("fs");

import fs from "fs";
import { move, rename } from "fs";

Let's say that we want to combine 2 arrays. Before spread operator you wrote

const arr1 = [1,2]
const arr2 = [3,4]
const result = arr1.concat(arr2)

Now we can use spread operator to merge 2 arrays.

const result = [...arr1, ...arr2]

What is does it spreads all values of arr1 inside our new array and then all values of arr2. As a result we get a new array. This is extremely easy to read and support.

It is also really useful when we need to create new object and avoid object mutation.

const oldState = {
  data: [],
  isLoading: false
const newState = {
  isLoading: true

So here we create a new state by spreading all properties of the old state and adding properties that we want to add or override.

elvis operator

Next important Javascript feature is optional chaining or elvis operator. It allows us to avoid ternary operators and undefined errors.

const baz = bar ? bar.baz : "default";

We can do the same with elvis operator like this.

const baz = bar?.baz || "default";

Which means that if bar is undefined it won't break but just return undefined of the whole construction. And you might say okay but it doesn't look that useful. What will you say if you want a deep nesting check?

const baz = (bar && bar.baz && bar.baz.foo) || "default";

This is extremely ugly and unsafe. The problem here that we use in and operators not really boolean but truthy which I can't recommend. Let's rewrite it with elvis operator.

const baz = bar?.baz?.foo || "default";

Much better and safer.

async await

For some reason people still write promises with then catch when we have async await and it's easier to read and support.

const someFn = () => {
    .then((res) => res.json)
    .then((data) => console.log("data"));

This code is fine but we get this nesting which doesn't allow us to combine easy several fetches for example and code doesn't look like synchronous code.

Let's rewrite it with async await.

const someFn = async () => {
  const res = await fetch("http://google.com");
  const json = await res.json();

Now it's easy to read, we can resolve as many promises as we want and code looks synchronous.

try catch

Which brings me to the last javascript feature which is extremely underrated. Programmers think only in happy path and that code will never break. Don't do it. Use throws and try catch to handle exceptions. Every async await that I showed you earlier can return a uncaught promise rejection because we must always wrap our async await code in try catch block.

const someFn = async () => {
  try {
    const res = await fetch("http://google.com");
    const json = await res.json();
  } catch (err) {

Thinking more pessimistic will make your code unbreakable and easier to support.

Also if you want to start debugging your code faster and easier check my 5 debugging hints right here.

🚨 Important