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 bothawait
in atry
block, and handle both errors in the correspondingcatch
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)
})()
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