Introduction

Hello and welcome to this introductory walkthrough on Javascript ES8. It's no mystery that JavaScript has undergone many changes in recent years. To learn about these new features, first, we're going to compare the ES8 and ES5 features in isolation, then we will build 2 apis in Node. One will be built using pure ES5 with the Express framework. Then we will mirror this api with ES8 and Koa. For the sake of simplicity in this tutorial, we will not be using any transpilers such as Babel. Instead, just make sure you have Node.js version 8 or above installed on your system and you will be able to keep up just fine. Let's get started

Getting Started

First let's open our terminal and set up our directories.

cd /your/project/directory
mkdir es5 es8 examples
cd examples
touch arrow-functions.js class.js control-flow.js destructuring.js import-export.js iteration.js let-const.js rest-spread.js string-template-literal.js

Good, now let's open let-const.js and write some code.

// normal variables
var x = 'hello'

// block scoped variable
let y = 'hello' // overall the same, but block scoped instead of lexical scoped

// block scoped immutable variable
const z = 'muahahaha'

First, you'll see we have a normal lexically scoped JavaScript variable. Same variable we have always known in JavaScript. Next we have this let variable. It is largely the same as var but instead of being lexically scoped, let is block scoped. Also, unlike a var which can be accessed even before it has been declared due to hoisting, let cannot be accessed until it has been declared. const is practically identical to let in every way except that it is immutable.

Let's move forward now and open arrow-functions.js.

// normal function
function add (x, y) {
  return x + y
}

// basic arrow function
const multiply = (x, y) => {
  return x * y
}

// when you have one parameter, you don't need parenthesis
const increment = x => {
  return x++
}

// and when you can fit your function in one line, you don't need brackets and return is implied
const incrementAgain = x => x++

// works for async functions too
const subtract = async (x, y) => {
  const value = await Promise.resolve(x + y)
  return value
}

This is fairly self-explanatory. Arrow functions are syntactic sugar to help make our code cleaner and easier to reason about. Take a quick look at multiply(), increment(), and incrementAgain(). To really get an idea of what arrow functions do.

Now let's open class.js.

// normal prototypal object creation
function Dog (x, y) {
  this.color = x
  this.breed = y
  this.hungerLevel = 0
}

Dog.prototype.changeBreed = function (x) {
  this.breed = x
}

Dog.prototype.increaseHunger = function () {
  if (this.hungerLevel < 5) {
    this.hungerLevel = 7
  } else {
    this.hungerLevel = 10
  }
}

var myDog = new Dog('brown', 'Great Dane')
myDog.changeBreed('Mastiff')
myDog.increaseHunger()

// now with ES6 Classes
class Dog {
  constructor(color, breed) {
    this.color = color
    this.breed = breed
    this.hungerLevel = 0
  }
  changeBreed (x) {
    this.breed = x
  }
  increaseHunger () {
    if (this.hungerLevel < 5) {
      this.hungerLevel = 7
    } else {
      this.hungerLevel = 10
    }
  }
  static incrementHunger () {
    if (this.hunger > 10) {this.hunger++}
  }
}

Again, this is largely syntactical sugar over Javascript's prototypal object creation model. You can see we have our constructor and our various methods. Beyond that, there is no special or add behavior to contend with.

Let's open import-export.js.

// normal CommonJS import
var fs = require('fs')
var http = require('http')
var {User, Task} = require('./some/model/file')

// imports with ES6 Modules
import fs from 'fs'
import http from 'http'
import {User, Task} from './some/model/file'

// normal CommonJS exporting
module.exports.add = function (x, y) {
  return x + y
}

// exports using ES6 modules
export const subtract = function (x, y) {
  return x - y
}

It's worth noting right quick that we won't be getting to use import/export in our project since Node doesn't support it yet at the time of this video. But we can see the functionality is fairly similar to CommonJs but we get a somewhat nicer syntax along with some other flexibility options that we won't go into in this tutorial.

Let's open iteration.js.

// let's say we have an array...
var x = [1, 2, 3, 4, 5]

// now let's iterate through it the normal js way and do stuff with the values...
var y = []
for (var i = 0; i < x.length; i++) {
  var number = x[i]
  y.push(number * 2)
}

// now let's iterate through this ES6 style
let z = []
for (let num of x) {
  z.push(num * 2)
}

// not bad, but ES6 also provides the ability to do this functionally
const a = x.map(num => num * 2)

// it also provides filter and reduce methods
const sumOfX = x.reduce((acc, num) => acc += num, 0)
const evenNumbers = x.filter(num => num % 2)

As you can see, we started with the ES5 way of iterating through an array. Next, we ran a for..of loop provided by ES8 which makes things significantly easier to reason about. However, since ES6, Javascript has been given new functional programming add ons. This is why next, you can see the functional version of the loops above being carried out using map. Plus, while we're at it, it's worth noting that Javascript also gives us filter and reduce methods now.

Let's open rest-spread.js.

// let's say we have a function that will call for a lot of parameters...
function sum() {
  for (var _len = arguments.length, numbers = Array(_len), _key = 0; _key < _len; _key++) {
    numbers[_key] = arguments[_key]
  }

  return numbers.reduce(function (accumulator, num) {
    return accumulator += num
  }, 0)
}
sum(1, 4, 12, 19) // --> 36

// not too bad, but we can do better now...
const newSum = (...numbers) => numbers.reduce((accumulator, num) => accumulator += num, 0)
newSum(1, 4, 12, 19) // --> 36

// ...numbers turns the parameters into an array that can be worked with. However, we can do even better...
const makeBigNumbers = (x, y, ...numbers) => {
  const z = x + y
  return numbers.map(num => num * z)
}

As you can see, both sum and newSum do the same things. But the ES8 version is significantly easier to reason about than the other. In this case we define a rest parameter with ...numbers which will turn all the function parameters into an accessible array called numbers.

Let's move forward and open string-template-literal.js

// normal string templating
var name = 'Mark'
var myString = "hello " + name

// new literal string templating indicated with backticks
var age = 29
var anAge = `my age is #{age}`

// we can also go to multiple lines with string template literals
var statement = `
  Hello everybody! I'm #{name} and I'm #{age}
  years old! How are you doing today?
`

This is far more self explanatory. We use backticks to define our string-template-literal. Two of the key benefits are that we get to use proper string interpolation as shown here and here. Plus, we can add multiple lines as needed. Fairly simple but it goes a long way in developer happiness.

Let's open destructuring.js.

// let's say that we have an object...
let obj = {
  valueOne: 'hello!',
  valueTwo: 'hello again!'
}

// now if we want to extract any values and store them in variables...
 const valueOne = obj.valueOne
 const valueTwo = obj.valueTwo

 // that's no fun... let's use destructuring to tidy this up
const {valueOne, valueTwo} = obj
console.log(valueOne) // --> 'hello!'
console.log(valueTwo) // --> 'hello again!'

// we can also give our destructured values aliases
const {valueOne: x, valueTwo} = obj

console.log(x) // --> 'hello!'
console.log(valueTwo) // --> 'hello again!'

// we also can destructure arrays...
const [a, b, c] = [1, 2, 3]
console.log(a) // --> 1
console.log(b) // --> 2
console.log(c) // --> 3

// and we can swap variables around without an auxiliary variable
let x = 0
let y = 1
const [x, y] = [y, x]
console.log(x) // --> 1
console.log(y) // --> 0

// we also get object literals which come in handy when passing values into functions.
User.findOne({username: username}) // --> old way
User.findOne({username}) // -- ES8 way

Destructuring is one of my personal favorites of the new features as I use it all the time. It allows us to quickly grab values out of an object and assign them to variables as seen here. It's important to note that these two sections are doing the exact same thing. We can also destructure arrays, swap variables, and pass object literals as needed.

Finally, let's open control-flow.js for possibly the biggest ES8 features.

// JS is infamous for its control flow issues
// stemming from the consistent issue of
// hard to read callback code

// Promises were first introduced as an alternate way
// to provide efficient asynchronous control flow
// as well as error handling to prevent callback hell

// importing the Bluebird promise library so we can
// promisify other callback based modules. We don't
// need it to be able to use promises themselves.
var Promise = require('bluebird')
var fs = Promise.promisifyAll(require('fs'))

// how normal callback code flow looks
function readLotsOfFiles (x, y, z) {
  fs.readFile(x, function (err, file) {
    if (err) {
      console.log(err)
    } else {
      fs.readFile(y, function (err, newFile) {
        if (err) {
          console.log(err)
        } else {
          fs.readFile(z, function (err, newerFile) {
            if (err) {
              console.log(err)
            } else {
              var list = [file, newFile, newerFile]
              return list
            }
          })
        }
      })
    }
  })
}

// now let's see the same thing with Promises ES6 style
function readLotsOfFiles (x, y, z) {
  let fileOne, fileTwo, fileThree
  const files = fs.readFileAsync(x)
    .then(file => {
      fileOne = file
      return fs.readFileAsync(y) // return a Promise which shows up as the parameter "newFile"
    })
    .then(newFile => {
      fileTwo = newFile
      return fs.readFileAsync(z)
    })
    .then(newerFile => {
      fileThree = newerFile
      return [fileOne, fileTwo, fileThree]
    })
    .catch(err => console.log(err))

  return files
}

// that's all well and good, however, this is ES8
// we can do better. Promises are integral to
// understanding Async/Await functions which are
// a major add on for ES8
// let's see how this same operation looks in an
// Async/Await function

async function readLotsOfFiles (x, y, z) {
  try {
    const fileOne = await fs.readFileAsync(x)
    const fileTwo = await fs.readFileAsync(y)
    const fileThree = await fs.readFileAsync(z)
    if (!fileOne || !fileTwo || !fileThree) {
      throw new Error('file read failed!')
    } else {return [fileOne, fileTwo, fileThree]}
  } catch (err) {
    console.log(err)
  }
}

// that's nice but now let's run the same operation concurrently
async function readLotsOfFilesConcurrently (x, y, z) {
  try {
    const fileOne = fs.readFileAsync(x) // remember this function call is returning a Promise
    const fileTwo = fs.readFileAsync(y)
    const fileThree = fs.readFileAsync(z)

    // use ES8 destructuring and Promise.all to run the operations concurrently
    const [file, newFile, newerFile] = await Promise.all([fileOne, fileTwo, fileThree])
    if (!file || !newFile || !newerFile) {throw new Error('files failed to read!')}
    return [file, newFile, newerFile]
  } catch (err) {
    console.log(err)
  }
}

It can't be stressed enough that each of these functions are doing the exact same thing. First, we have our callback version which needs no introduction. Next we use Promises which were introduced to JS in ES6. Pay no mind to the use of Bluebird here, we're only using it to promisify libraries which are built to use callbacks. Lastly we have the async/await versions of the function. We won't go into the hardcore details of how async/await works but you can see the gist of how it works. If a function returns a Promise, you can await for the value to finish. This gives us the ability to write asynchronous code that looks more like synchronous code. If you're confused, don't worry, there will be links below for everything that we have gone over.

Summary

So we covered a lot for one video. This was meant to give you some basic exposure to these concepts as we will be using these in our upcoming APIs. If you feel lost on any of these, it's okay. We have some helpful links below which will help guide you on the heavy details of any ES6/ES8 feature.

Resources

Franzé Jr

Software Engineer with experience working in multi-cultural teams, Franze wants to help people when he can, and he is passionate about programming and Computer Science. Founder of RemoteMeetup.com where he can meet people all over the World. When Franze is not coding, he is studying something about programming.

  1. Comments for Compare ES8 and ES5

You must login to comment

You May Also Like