DEV Community

Connor Dillon
Connor Dillon

Posted on

5 Wonderful Javascript Tricks

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')
Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

import { debounce } from 'lodash'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

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

// OK Practice:
const requests = URLs.map((url) => fetch(url))
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
})()
Enter fullscreen mode Exit fullscreen mode

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))
})()
Enter fullscreen mode Exit fullscreen mode

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)
  }
})()
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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'))
Enter fullscreen mode Exit fullscreen mode

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)
})()
Enter fullscreen mode Exit fullscreen mode

Latest comments (1)

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