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 bothawaitin atryblock, and handle both errors in the correspondingcatchblock.
(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)
})()
Top comments (1)
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:
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.
More info about NodeList on MDN WebDocs