loading...

5 Wonderful Javascript Tricks

connoro7 profile image Connor Dillon ・4 min read

Javascript Tricks

Query Selector

You can get elements through the DOM API with methods like getElementsByClassName or getElementById, but these can both be replaced with querySelector.

querySelector works like a CSS selector, using a period . to select classes and pounds # to select IDs. For example:

const classElement = document.querySelector('.myClass')
const idElement = document.querySelector('#myID')

querySelector is also able to target sub elements, which is not available to getElementById or getElementsByClassName. Use the space syntax to get the direct child, or the caret > syntax to get all children.

querySelectorAll gets you all of the matches and returns an array of nodes.

// sub elements
const subElement = document.querySelector('.myClass > li')
// return multiple elements
const allDivs = document.querySelectorAll('div')

Array Methods

Some array methods are overrated, and some are underrated:

reduce

reduce is underrated, especially by new developers - it takes a while to understand it's full power. reduce gives you an accumulator and a value in your callback function, but the accumulator doesn't have to be a number! -- It can be anything: an object, an array, a string,

const nums = [1, 2, 3]
const sum = nums.reduce((a, v) => a + v, 0) // Best practice: accumulator value defaults to first element of the object/array, so be sure to specify a default of zero to avoid typeof() errors.
const sum = nums.reduce((a, v) => [a + v], []) // object, [ '123' ]
const sum = nums.reduce((a, v) => a.concat(v), []) // array, [1, 2, 3]
const sum = nums.reduce((a, v) => a + v, '') // string, '123'

reduce allows you to very easily convert arrays to hashmaps or objects, build up strings, build up a variation of the original array

const veggies = [
    {name: 'potato', price: 1}
    {name: 'onion', price: 2}
    {name: 'cucumber', price: 1}
]
const veggiePrices = veggies.reduce(
    (a,v) => {
        a[v.name] = v.price
        return a
    },
    {}
)
console.log(veggiePrices) // { potato: 1, onion: 2, cucumber: 1 }
// Creates {name: price} object

reduce is powerful enough to do the job of map, sort, and filter if you write the right callback function.

Destructuring Tricks

Destructuring is (almost) unique to Javascript, and allows you to pull out one (or more) properties from an object, and is heavily used in frameworks such as Angular or React.

const veggie = { name: 'potato', price: 1 }
const { price } = veggie
console.log(price) // 1

Destructuring is also used anytime that you're importing libraries:

import { debounce } from 'lodash'

Destructuring can also be used as a parameter to a function:

const veggie = { name: 'potato', price: 1 }
function printPrice({ price }) {
  console.log(price)
}
printPrice(veggie) // 1

Destructuring can also be nested, going multiple layers deep into an object:

const veggiePrices = {
  potatoes: {
    red: 1,
    gold: 2,
  },
}

const veggiePrices = { potatoes: { red } }
console.log(red) // 1

Destructuring can also be used directly after writing an array method. For example, if you want to sort an array and then pull out the first item, you can easily do that with destructuring

const nums = [3, 1, 2]
const [first] = nums.sort()
console.log(first)

Promise Tips

Promises are asynchronous because they run in the background relative to the code you see on the screen.

const res = fetch('https://google.com')
const res2 = fetch('https://facebook.com')
const res3 = fetch('https://instagram.com')

Promises can be treated like any other object and store them in data structures.

const URLs = ['https://google.com', 'https://facebook.com', 'https://instagram.com']

You can use the map function to create an array of promises:

// OK Practice:
const requests = URLs.map((url) => fetch(url))

But do we want the promises to run at the same time or one after the other?

To run them at the same time, use the Promise.all() function. This ties all of the promises together, such that the array resolves after all of the promises resolve. The result is an array of the results, some of which may be failures, so this will also handle your errors.

const responses = Promise.all(requests)

If you want the promises to run one after another, you need to be careful!

You can use async await, but keep in mind your awaits need to be inside an async function.

// Better Practice - Make it an async IIFE!
(async () => {
  const requests = URLs.map((url) => fetch(url))
  const responses = Promise.all(requests)
})()

You also can't run an await inside an array method! So if you tried to map() by using an await inside of a map() or forEach(), it's not going to work.

To fix this, you can write await inside a map function, you just need to make it an async map function.

// !!! Won't work !!!
(async () => {
  const requests = URLs.map(url => await fetch(url))
})()

// !!! Works !!!
(async () => {
  const requests = async URLs.map(url => await fetch(url))
})()

In order to get this to work, you can to use a for(){} or forOf(){} loop.

(async () => {
  const responses = []
  for (let url of URLs) {
    const res = await fetch(url)
    responses.push(res)
  }
})()

Error Handling

Error handling is made super easy with a try{} catch(){} block.

try {
  // code that might throw Error
} catch (err) {
  // what do if Error is thrown
}

You can also handle errors with a .catch() on a promise. For example, when using a .then() chain, you can append a .catch() to handle errors that might have occured along the way.

fetch('https://google.com')
  .then((res) => {
    res
      .json()
      .then((json) => {
        console.log('got json', json)
      })
      .catch((err) => console.error('json failed'))
  })
  .catch((err) => console.error('request failed'))

If using async await on the above example, we can either throw an await in a try catch block, OR we can append a .catch() on the end of the await syntax. This is much cleaner than the above example, because we're combining traditional promise syntax with await.

If you have the same error handler for both await, you can just wrap both await in a try block, and handle both errors in the corresponding catch block.

(async () => {
  const res = await fetch('').catch((err) => console.error(err))
  const json = await res.json().catch((err) => console.log(err))
  console.log('got json', json)
})()

Discussion

pic
Editor guide
Collapse
gilfewster profile image
Gil Fewster

Nice article, Connor.

Just a quick note for querySelectorAll, which can trip up developers when they first begin to use it.

querySelectorAll(target) doesn't return an Array, it returns an array-like object called NodeList. Superficially, the two look the same -- both have length properties, both can be iterated with for & forEach loops, and both allow you to access individual items using square bracket notation.

But NodeList does not have a number of methods that we routinely use on Arrays, including:

  • Push/Pop/Shift/Unshift
  • Splice/Slice
  • Join
  • Sort/Filter/Map/Reduce

Attempting to use these methods directly on an object return from querySelectorAll() will generate an error.

Happily, you can convert a NodeList into an Array using the Array.from() method.

const paras = document.querySelectorAll("p");
const paraLengths = paras.map(para => para.innerText.length);
//  error: paras.map is not a function"
const parasArray = Array.from(document.querySelectorAll("p"));
const paraLengths = parasArray.map(para => para.innerText.length);
// success -- paraLengths array contains the number of characters in each paragraph.

More info about NodeList on MDN WebDocs