Notes Week 5

ES6 Syntax

There was lots of new syntax introduced to JavaScript over the past several years, which makes our lives easier.

Arrow Functions
// old syntax
const add =  function(a, b) {
  return a + b
}

// new syntax
const add = (a, b) => a + b

add(1, 2) // 3
The Ternary Operator
// old syntax
let num
if (condition) {
  num = 1
}
else {
  num = 2
}

// new syntax
const num = condition ? 1 : 2
Template Strings
const name = "Jolene"
const color = "Auburn"

// old syntax
const text = "My name is " + name + " and I have " + color + " hair."

// new syntax
const text = `My name is ${name} and I have ${color} hair`
Destructuring
const jolene = {
  name: "Jolene",
  age: 18,
  eyes: "emerald green"
}

// old syntax
const name = jolene.name
const age = jolene.age
const eyes = jolene.eyes

// new syntax
const { name, age, eyes } = jolene
const likes = ["pizza", "myself", 420]

// old syntax
const food = likes[0]
const person = likes[1]
const number = likes[2]

// new syntax
const [food, person, number] = likes
Default Values
// old syntax
function addOne(num) {
  return num + 1
}

// new syntax
function addOne(num = 0) {
  return num + 1
}

addOne() // old => NaN, new => 1
Shorthand for Objects
const name = "Jolene"
const hairColor = "Auburn"
const beautyLevel = "Beyond Compare"

// old syntax
const jolene = {
  name: name,
  hairColor: hairColor,
  beautyLevel: beautyLevel
}

// new syntax
const jolene = {
  name,
  hairColor,
  beautyLevel
}
The Spread/Rest Operator
const jolene1 = {
  name: "Jolene",
  age: 18
}
const jolene2 = {
  skinColor: "ivory",
  voiceSoftnessLevel: "like summer rain"
}

// old syntax
const combinedJolene = {
  name: jolene1.name,
  age: jolene1.age,
  skinColor: jolene2.skinColor,
  voiceSoftnessLevel: jolene2.voiceSoftnessLevel
}

// new syntax
const combinedJolene = {
  ...jolene1,
  ...jolene2
}
const someThings = [
  "raindrops",
  "roses"
]
const moreThings = [
  "whiskers on kittens",
  "brown paper packages tied up with string"
]

// old syntax
const allFavThings = [
  someThings[0],
  someThings[1],
  moreThings[0],
  moreThings[1]
]

// new syntax
const allFavThings = [
  ...someThings,
  ...moreThings
]
Array Prototype Methods

All objects come with a __proto__ property, which contains a list of properties and methods that we get for free, based on the class of object that we’re accessing.

For arrays, there are a few super useful methods that you should be aware of:

  • Array.push() - add a new item to the array
  • Array.forEach() - run a function for every item in the array
  • Array.map() - mutate every item in the array
  • Array.filter() - filter out some items in the array
  • Array.reduce() - create a new value by looping over the array

Object Orientated Programming

Classses and Objects

In JavaScript, we often want to represent things in the real world as objects inside our programs. For example:

const jolene = {
  name: "Jolene",
  age: 21
}

We can define properties and methods on these objects, to add more and more color for how they behave in the real world.

We often find that we have overlapping code when we try to create similar objects:

const apple = {
  name: "apple",
  taste: "sweet",
  shape: "round",
  eat: () => console.log("Mmmm"),
  throw: () => console.log("Take this!")
}

const banana = {
  name: "banana",
  taste: "semi-sweet",
  shape: "arced",
  eat: () => console.log("Mmmm"),
  throw: () => console.log("Take this!")
}

We’re repeating functionality for our eat() and throw() methods here, meaning that we’re not able to keep our code DRY (Don’t Repeat Yourself!).

We solve this by using classes, which allow us to factor out common functionality for objects.

Classes in JavaScript are sort of like cookie cutters to a cookie. A class is a template or blueprint, which we can use to create an object (AKA a class instance).

Class Syntax
class Fruit {
  constructor(name, taste, shape) {
    this.name = name
    this.taste = taste
    this.shape = shape 
    this.eaten = false
  }

  eat() {
    if (!this.eaten) {
      console.log("Nom non nom")
      this.eaten = true
    }
    else {
      console.log("Awww... I ate that one already")
    }
  }
}

Loading Data

HTTP Requests

The way that we communicate to servers is through HTTP requests. After receiving our HTTP requests, the server can then respond to us with an HTTP response.

There are a few different types of HTTP request that we can send:

  • GET - give me something
  • POST - create something new
  • PATCH - edit something
  • PUT - replace something
  • DELETE - delete something
APIs Explained

API stands for Application Programming Interface - it describes how we can interact with our code on our server.

An API typically consists of a set of routes, which define how we can interact with our server.

Typically, when we ask for and send data to our APIs, we make use of JavaScript Object Notation (JSON), which is a type of data fornat. JSON looks almost identical to an array of JavaScript objects, which makes it easy to handle in our code.

Asynchronous vs Synchronous Operations

When we make an HTTP request to a server, this takes a non-zero amount time, and so we say that it is an asynchronous operation (as opposed to a console.log() statement, which gets executed immediately - as soon as the browser sees it).

Since JavaScript can only do one thing at a time (it is a single threaded language), we typically get around this by using a callback function:

// a common design pattern in JavaScript
const callback = (result, error) => {
  if (error) { console.log(error) }
  console.log(result)
}
asynchronousGetDataFunction(callback)

With this syntax, the asynchronousGetDataFunction will execute immediately, but it won’t hold up the rest of the program’s execution, because it will simply wait until it receives a response, at which point it will call the callback function.

This can be troublesome, because we have to nest subsequent asynchronous calls inside of each other, leading to the eventual problem of callback hell, which makes it difficult to read what our program is doing.

Promises

We get around callback hell by using Promises. Promises are values in JavaScript which can be either pending, resovled, or rejected.

Rather than having to wait for our asynchronous call to resolve before we can keep the rest of the program running, Promises let us pass this indeterminate value around in our code, whilst it is still being resolved.

With a Promise, we can use the then() method to define the callback function that we should use in the case that the Promise is eventually resolved with no errors. We can also use the catch() method to handle any errors:

getData()
  .then(result => {
    // resolved result
    console.log(result)
  })
  .catch(error => {
    // this happens when Promise is rejected
    console.log(error)
  })
HTTP Requests in JavaScript

Typcially, when we want to make a GET request inside our JavaScript file, we can do it like this:

fetch("path/to/url")
  .then(response => response.json())
  .then(data => {
    console.log(data)
    // do stuff with the data here!
  })
  .catch(error => console.log(error))

fetch() is a function that we get from JavfaScript for free, which we canuse to make HTTP requests. By default, fetch will try to make a GET request, unless we tell it otherwise.

Calling fetch() with a URL returns a Promise of the HTTP response that we get back. When this resolves, then we hit the first then() method in this chain. The response object has a method call json(), which also returns a promise, which eventually resolves to the data that we were asking for.

Async / Await Syntax

There’s a slighly nicer way to write this same code, using the “async/await” syntax:

async function getData() {
  const response = await fetch("path/to/url")
  const data = await response.json()
  console.log(data)
}

getData() // invoke this function
Try / Catch Syntax

If we write our code with the async/await syntax, then we’ll want to handle errors using a try/catch block:

async function getData() {
  try {
    const response = await fetch("path/to/url")
    const data = await response.json()
    console.log(data)
  }
  catch (error) {
    console.log(error)
  }
}

If one of our await statements results in a rejected Promise in this case, then we will hit the catch block, which will show us what the error is.