Modern JS with ES6+
Published: 2021-12-17 22:46:42

---
title: Modern JS with ES6+
published: true
date: 2018-04-08 05:35:00 UTC
tags: 
canonical_url: https://kevinlangleyjr.dev/blog/modern-js
---

This is the accompanying blog post to my [Modern JS with ES6](https://www.youtube.com/watch?v=2CQO-o5lXSE) talk for the [_Learn JavaScript Deeply_](https://twitter.com/wordcampmiami/status/961616862214672384) course I taught at [WordCamp Miami 2018](https://miami.wordcamp.org/2018/).

Today in 2018, JavaScript is the most popular language to use on the web, according to the [StackOverflow 2018 developer survey](https://insights.stackoverflow.com/survey/2018#technology).

But the JavaScript we know and love today, is not the _same_ JavaScript from the 2000's or even the early 2010's.

Let's dive in!

## ES_???

ES is short for ECMAScript. Every time you see ES followed by a number, it is referencing that version of ECMAScript.

Only recently, with ES6 or ES2015 did JavaScript make the biggest leap in syntax and added functionality. These additions were intended to make large-scale JavaScript development easier.

- ES1: June 1997
- ES2: June 1998
- ES3: December 1999
- ES4: Abandoned
- ES5: December 2009
- ES6/ES2015: June 2015
- ES7/ES2016: June 2016
- ES8/ES2017: June 2017
- ES9/ES2018: June 2018
- ES10/ES2019: June 2019
- ES11/ES2020: June 2020
- ES.Next: This term is dynamic and references the next version of ECMAScript coming out.

## TC39

The [TC39](https://tc39.es/) is the group that is responsible for advancing the ECMAScript specifications and standardizing these specifications for the JavaScript language.

All changes to the specification are developed within a process that evolves a feature from the idea phase to a fully fledged and specified feature.

- Stage 0 - Strawperson
- Stage 1 - Proposal
- Stage 2 - Draft
- Stage 3 - Candidate
- Stage 4 - Finished

You can see an active list of proposals for the TC39, [here](https://tc39.es/#proposals).

## Modules

Modules allow you to load code asynchronously and provides a layer of abstraction to your code. While JavaScript has had modules for a long time, they were previously implemented with libraries and not built into the language itself. ES6 is the when the JavaScript language first had built-in modules.

There are two ways to export from a module.

- Named exports
- Default export

### Named Exports

With named exports, you just prepend what you want to export with `export`.

```js
// mathlib.js

export function square(x) {
  return x * x;
}

export function add(x, y) {
  return x + y;
}
```


And then they can be imported using the same name as the object you exported.


```js
// main.js

import { square, add } from './mathlib';

console.log(square(9)); // 81

console.log(add(4, 3)); // 7
```


### Default Export

With default exports, you just prepend what you want to export with `export default`.


```js
// foo.js
export default () => {
  console.log('Foo!');
}
```

```js
// main.js
import foo from './foo';

foo(); // Foo!
```


### Default and Named Exports

The two ways can even be mixed!

```js
// foobar.js

export default () => {
  console.log('Foo!');
}

export const bar() => {
  console.log( 'Bar!' )
}
```

```js
// main.js

import foo, { bar } from './foobar';

foo(); // Foo!

bar(); // Bar!
```


## Variable Scoping

### `var` vs `let` vs `const`

`var` is either function-scoped or globally-scoped, depending on the context.

If a `var` is defined within a function, it will be scoped to that enclosing function as well as any functions declared within. And would be globally scoped if it is declared outside of any function.

```js
if ( true ) {
  var foo = 'bar';
}

console.log( foo ); // bar

function hello() {
  var x = 'hello';
  function world() {
    var y = 'world';
    console.log(x); // hello (function `hello()` encloses `x`)
    console.log(y); // world (`y` is in scope)
  }
  world();
  console.log(x); // hello (`x` is in scope)
  console.log(y); // ReferenceError `y` is scoped to `world()`
}

hello();
```


`let` and `const` are block scoped.


```js
if ( true ) {
  let foo = 'bar';
  const bar = 'foo';
}

console.log( foo ); // ReferenceError.
console.log( bar ); // ReferenceError.
```


You can create new block scopes with curly brackets `{}` as shown in the below code sample.

`let` and `const`


```js
let first = 'First string';

{ // Each layer of curly brackets gives us a new block scope.
  let second = 'Second string';
    {
      let third = 'Third string';
    }
    // Accessing third here would throw a ReferenceError.
}
// Accessing second here would throw a ReferenceError.
```

`const` variables can only be assigned once. But it is NOT immutable.


```js
const foo = { bar: 1 };

foo = 'bar'; // 'foo' is read only.
```

But, you can change the properties!

```js
const foo = { bar: 1 };

foo.bar = 2;
console.log(foo); // { bar: 2 }
```

`Object.freeze()` prevents changing the properties. Freezing an object returns the originally passed in object and has a multitude of effects on the object.

It will prevent new properties from being added to it, existing properties from being removed, changing the value of properties, and even prevents the prototype from being changed as well.

The object's properties are made immutable with `Object.freeze()`

```js
const foo = { bar: 1 };

Object.freeze(foo);
foo.bar = 3; // Will silently fail or in strict mode, will return a TypeError
console.log(foo.bar); // 2
```
  
Another option you have is to **seal** an object to prevent changing the object structure.

<img alt="Seal" src="https://res.cloudinary.com/kevinlangleyjr-dev/image/upload/v1620847917/blog/modern-js/7093270813_fdafd6e704_o_f1brtj.jpg" alt="Seal performs at Sydney Entertainment Centre, Australia"> 
[Photo Credit: Eva Rinaldi](https://www.flickr.com/photos/evarinaldiphotography/7093270813)

**Wait, no. Not that Seal!**

<img alt="Seal" src="https://res.cloudinary.com/kevinlangleyjr-dev/image/upload/v1620844147/blog/modern-js/amy-asher-giZJHm2m9yY-unsplash_ts6ic7.jpg" alt="A Cute Seal">
[Photo Credit: Amy Asher](https://unsplash.com/photos/giZJHm2m9yY)

**Yeah, no. Not that one either...**

```js
const foo = { bar: 1 };

Object.seal(foo);
foo.baz = false; // Will silently fail or in strict mode, will return a TypeError
foo.bar = 2;
console.log( foo ); // { bar: 2 }
```


## Hoisting

Hoisting in JavaScript is a process where variables and function declarations are moved to the top of their scope before code execution.

Which means you can do this with functions and vars:

```js
sayHello();

function sayHello() {
  console.log('Hello!');
}
```

```js
console.log( foobar ); // undefined (note: not ReferenceError!)
var foobar = 'Woot!'
```

In ES6, classes, `let`, and `const` variables are hoisted but they are not initialized yet unlike `var` variables and functions.

```js
new Thing(); // TypeError
class Thing{};

console.log(foo); // 'foo' was used before it was defined
let foo = true;

console.log(bar); // 'bar' was used before it was defined
const bar = true;
```

## Temporal Dead Zone

<img alt="TARDIS" src="https://res.cloudinary.com/kevinlangleyjr-dev/image/upload/v1631465914/blog/modern-js/25031662202_51c0be73c9_o_klr3xa.jpg" alt="TARDIS">
[Photo Credit: Giles Turnbull](https://www.flickr.com/photos/gilest/25031662202/)

While the temporal dead zone sounds like something from the plot of a Doctor Who episode, it is way less scary than it sounds.

A `const` or `let` variable is in a temporal dead zone from the start of the block until the initialization is processed.


```js
if ( true ) {

  // TDZ starts!
  const doSomething = function () {
    console.log( thing ); // OK!
  };

  doSomething(); // ReferenceError
  let thing = 'test'; // TDZ ends.
  doSomething(); // Called outside TDZ!
}
```

Referencing the variable in the block before the initialization results in a `ReferenceError`, contrary to a variable declared with `var`, which will just have the `undefined` value and type.

## But, what should I use?!? `var`? `let`? `const`?

The only difference between `const` and `let` is that `const` makes the contract that no rebinding will happen.

> Use `const` by default. Only use `let` if rebinding is needed.`var` shouldn't be used in ES2015.
> 
> Mathias Bynens - V8 Engineer @ Google

> Use `var` for top level variables Use `let` for localized variables in smaller scopes. Refactor `let` to `const` only after some code has been written and you're reasonably sure there shouldn't be variable reassignment.
> 
> Kyle Simpson - Founder @ Getify Solutions

I, personally, follow the first approach of using `const` by default, never use `var`, and only use `let` when I know I need to reassign the variable.

## Iterables & Looping

When iterating or looping using `var`, you leak a global variable to the parent scope and the variable gets overwritten with every iteration.

```js
for ( var i = 0; i < 10; i++ ) {
  setTimeout( function() {
    console.log( 'Number: ' + i );
  }, 1000 );
}

// Number: 10
// Number: 10
// Number: 10
// Number: 10
// Number: 10
// Number: 10
// Number: 10
// Number: 10
// Number: 10
// Number: 10
```

Using `let` in a `for` loop allows us to have the variable scoped to its block only.

```js
for ( let i = 0; i < 10; i++ ) {
  setTimeout( function() {
    console.log( 'Number: ' + i );
  }, 1000 );
}

// Number: 0
// Number: 1
// Number: 2
// Number: 3
// Number: 4
// Number: 5
// Number: 6
// Number: 7
// Number: 8
// Number: 9
```


### ES6 also gives us a new way to loop over iterables!

Using a `for...of` statement loops over an iterable with a very clean and simple syntax.

```js
const iterable = [10, 20, 30];

for (const value of iterable) {
  console.log(value);
}

// 10
// 20
// 30
```

You can even iterate over `NodeLists` without having to use any other trickery! 🤯

```js
const articleParagraphs = document.querySelectorAll('article > p');

for (const paragraph of articleParagraphs) {
  paragraph.classList.add('read');
}
```

<img alt="Mind Blown Gif" src="https://res.cloudinary.com/kevinlangleyjr-dev/image/upload/v1631466460/blog/modern-js/blow-mind-mind-blown_oz6qpc.gif">

Or even, use it to iterate over letters in a string!

```js
const foo = 'bar';
for (const letter of foo) {
  console.log(letter);
}

// b
// a
// r
```


## Arrow Functions

Arrow functions are a more concise alternative to the traditional function expression.

```js
// Traditional function expression.
const addNumbers = function (num1, num2) {
  return num1 + num2;
}

// Arrow function expression.
const addNumbers = (num1, num2) => {
  return num1 + num2;
}
```

Arrow functions can have implicit returns:

```js
// Arrow function with implicit return and without any arguments.
const sayHello = () => console.log( 'Hello!' );

// Arrow function with implicit return and a single argument.
const sayHello = name => console.log( `Hello ${name}!` );

// Arrow function with implicit return and multiple arguments.
const sayHello = (firstName, lastName) => console.log( `Hello ${firstName} ${lastName}!` );
```

### What about `this`?

With the introduction of ES6, the value of `this` is picked up from its surroundings or the function's enclosing lexical context. Therefore, you don't need `bind()`, `that`, or `self` anymore!

```js
function Person(){
  this.age = 0;

  setInterval(function() {
    this.age++; // `this`refers to the Window 😒
  }, 1000);
}
```

```js
function Person(){
  var that = this;
  this.age = 0;

  setInterval(function() {
    that.age++; // Without arrow functions. Works, but is not ideal.
  }, 1000);
}
```

```js
function Person(){
  this.age = 0;

  setInterval(() => {
    this.age++; // `this` properly refers to the person object. 🎉🎉🎉
  }, 1000);
}
```

### When should I not use arrow functions?

Check out my article, about [when to not use arrow functions in JavaScript](https://dev.to/kevinlangleyjr/when-to-not-use-arrow-functions-in-javascript-352h-temp-slug-140274)

## Default Arguments

Here's a basic function.

```js
function calculateTotal( subtotal, tax, shipping ) {
return subtotal + shipping + (subtotal * tax);
}

const total = calculateTotal(100, 0.07, 10);
```

Let’s add some defaults to the arguments in our function expression!

The old way 😕

```js
function calculateTotal( subtotal, tax, shipping ) {
  if ( tax === undefined ) {
    tax = 0.07;
  }

  if ( shipping === undefined ) {
    shipping = 10;
  }

  return subtotal + shipping + (subtotal * tax);
}

const total = calculateTotal(100);
```

A little better

```js
function calculateTotal( subtotal, tax, shipping ) {
  tax = tax || 0.07;
  shipping = shipping || 10;

  return subtotal + shipping + (subtotal * tax);
}

const total = calculateTotal(100);
```

Now with ES6+! 🎉🎉🎉

```js
function calculateTotal( subtotal, tax = 0.07, shipping = 10 ) {
  return subtotal + shipping + (subtotal * tax);
}

const total = calculateTotal(100);
```

### What if I wanted to only pass in the first and third argument?

```js
function calculateTotal( subtotal, tax = 0.07, shipping = 10 ) {
  return subtotal + shipping + (subtotal * tax);
}

const total = calculateTotal(100, , 20); // SyntaxError! Cannot pass empty spaces for argument.

const total = calculateTotal(100, undefined, 20); // 🎉🎉🎉🎉
```

## Destructuring

Destructuring allows you to extract multiple values from any data that is stored in an object or an array.

```js
const person ={
  first: 'Kevin',
  last: 'Langley',
  location: {
    city: 'Beverly Hills',
    state: 'Florida'
  }
};
```

Using the above object, let's create some variables from the object's properties.

```js
const first = person.first;
const last = person.last;
```

Before ES6, we were stuck just initializing a variable and assigning it to the object property you'd like to extract.

```js
const { first, last, location } = person;

const { city, state } = location;
```

But with ES6+, we're able to destructure the variable and create new variables from the extracted data.

You can also alias the extracted data to a different variable name!

```js
const { first: fName, last: lName } = person;

const { city: locationCity, state: locationState } = person.location;
```

It even works with nested properties!

```js
const { first, last, location: { city } } = person;

console.log( city ); // Beverly Hills
```


### What if I tried to destruct a property that doesn't exist?

The returned value for that variable will be `undefined`.

```js
const settings = { color: 'white', height: 500 };
const { width, height, color } = settings;

console.log(width);// undefined
console.log(height); // 500
console.log(color);// white
```

You can even set defaults in your destructuring!

```js
const settings = { color: 'white', height: 500 };
const { width = 200, height = 200, color = 'black' } = settings;

console.log(width);// 200
console.log(height); // 500
console.log(color);// white
```

You can destructure arrays as well!

```js
const details = ['Kevin', 'Langley', 'kevinlangleyjr.com'];
const [first, last, website] = details;

console.log(first);// Kevin
console.log(last);// Langley
console.log(website); // kevinlangleyjr.com
```

## Spread... and ...Rest

Before ES6, we would run `.apply()` to pass in an array of arguments.

```js
function doSomething (x, y, z) {
  console.log(x, y, z);
}

let args = [0, 1, 2];

// Call the function, passing args.
doSomething.apply(null, args);
```

### ...Spread Operator

But with ES6, we can use the spread operator `...` to pass in the arguments.

```js
function doSomething (x, y, z) {
  console.log(x, y, z);
}

let args = [0, 1, 2];

// Call the function, without `apply`, passing args with the spread operator!
doSomething(...args);
```

We can also use the spread operator to combine arrays.

```js
let array1 = ['one', 'two', 'three'];
let array2 = ['four', 'five'];

array1.push(...array2) // Adds array2 items to end of array.
array1.unshift(...array2) //Adds array2 items to beginning of array.
```

And you can combine them at any point in the array!

```js
let array1 = ['two', 'three'];
let array2 = ['one', ...array1, 'four', 'five'];

console.log(array2); // ["one", "two", "three", "four", "five"]
```

We can also use the spread operator to create a copy of an array.

```js
let array1 = [1,2,3];
let array2 = [...array1]; // like array1.slice()

array2.push(4);

console.log(array1); // [1,2,3]
console.log(array2); // [1,2,3,4]
```

We can also use the spread operator with destructuring.

```js
const players = ['Kevin', 'Bobby', 'Nicole', 'Naomi', 'Jim', 'Sherry'];
const [first, second, third, ...unplaced] = players;

console.log(first); // Kevin
console.log(second); // Bobby
console.log(third); // Nicole
console.log(unplaced); // ["Naomi", "Jim", "Sherry"]
```

```js
const { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };

console.log(x); // 1
console.log(y); // 2
console.log(z); // { a: 3, b: 4 }
```

We can also use the spread operator to expand a NodeList.

```js
const elements = [...document.querySelectorAll('div')];

console.log(elements); // Lists all the div's on the page.
```

### ...Rest Operator

The rest operator allows us to more easily handle a variable number of function parameters.

```js
function doMath(operator, ...numbers) {
  console.log(operator); // 'add'
  console.log(numbers); // [1, 2, 3]
}

doMath('add', 1, 2, 3);
```

## Template Literals

The template literal, introduced in ES6, is a new way to create a string.

```js
const name = 'Kevin';

// The old way...
console.log('Hello, ' + name + '!'); // Hello, Kevin!
```

```js
const name = 'Kevin';

// With ES6 template literals.
console.log(`Hello, ${name}!`); // Hello, Kevin!
```

Within template literals you can evaluate expressions.

```js
const price = 19.99;
const tax = 0.07;
const total = `The total price is ${price + (price * tax)}`;

console.log(total); // The total price is 21.3893
```

With template literals you can more easily create multi-line strings.

```js
console.log('This is some text that flows across\ntwo lines!');

// This is some text that flows across
// two lines!

console.log(`But so does
this text!`);

// But so does
// this text!
```

## New String Methods

With ES6, we have a few new string methods that can be very useful.

### .startsWith()

This method returns a bool of whether the string begins with a substring, starting at the index provided as the second argument, which defaults to `0`.

#### Syntax

```js
startsWith( searchString )
startsWith( searchString, position )
```

#### Examples

```js
const str = 'Learn JavaScript Deeply';

console.log(str.startsWith('Learn'));// true
console.log(str.startsWith('JavaScript'));// false
console.log(str.startsWith('Deeply', 17));// true
```

### .endsWith()

This method returns a bool of whether the string ends with a substring, with the optional parameter of length which is used as the length of the `str` and defaults to `str.length`.

#### Syntax

```js
endsWith( searchString )
endsWith( searchString, length )
```

#### Examples

```js
const str = 'Learn JavaScript Deeply';

console.log(str.endsWith('Deeply'));// true
console.log(str.endsWith('Learn'));// false
console.log(str.endsWith('JavaScript', 16));// true
```

### .includes()

This method returns a bool of whether the string includes a substring, starting at the index provided as the second argument, which defaults to `0`.

#### Syntax

```js
includes( searchString )
includes( searchString, position )
```

#### Examples
```js
const str = 'Learn JavaScript Deeply';

console.log(str.includes('JavaScript'));// true
console.log(str.includes('Javascript'));// false
console.log(str.includes('PHP'));// false
```


### .repeat()

This method returns a new string which contains a concatenation of the specified number of copies of the original string.

#### Syntax
```js
repeat( count )
```

#### Examples
```js
const str = 'Deeply';

console.log(str.repeat(3));// DeeplyDeeplyDeeply
console.log(str.repeat(2.5));// DeeplyDeeply (converts to int)
console.log(str.repeat(-1));// RangeError
```

## Enhanced Object Literals
```js
const first = 'Kevin';
const last = 'Langley';
const age = 29;
```

Let's assign our variables to properties of an object!

```js
const person = {
  first: first,
  last: last,
  age: age
};
```

Now let's do it again but with object literals this time.

```js
const person = {
  first,
  last,
  age
};
```

You can even mix object literals with normal key value pairs.

```js
const person = {
  firstName: first,
  lastName: last,
  age
};
```

We can also use a shorter syntax for method definitions on objects initializers.

The syntax we're all familiar with in ES5...

```js
var obj = {
  foo: function() {
    console.log('foo');
  },
  bar: function() {
console.log('bar');

}

};
```

Can now be simplified with the new syntax for method definitions!

```js
const obj = {
  foo() {
    console.log('foo');
  },
  bar() {
    console.log('bar');
  }
};
```

Or even define keys that evaluate on run time inside object literals.

```js
let i = 0;
const a = {
  ['foo' + ++i]: i,
  ['foo' + ++i]: i,
  ['foo' + ++i]: i
};

console.log(a.foo1); // 1
console.log(a.foo2); // 2
console.log(a.foo3); // 3
```

Let's clean that up a bit with string template literals for the keys!

```js
let i = 0;
const a = {
  [`foo${++i}`]: i,
  [`foo${++i}`]: i,
  [`foo${++i}`]: i
};

console.log(a.foo1); // 1
console.log(a.foo2); // 2
console.log(a.foo3); // 3
```

## New Array Methods!

### Array.find()

The `Array.find` static method returns the first value in the provided array that satisfy the testing function or undefined if no elements match.

#### Syntax
```js
// Arrow function
Array.find( element => { conditional } )
Array.find((element, index) => { conditional } )
Array.find((element, index, array) => { conditional } )

// Callback function
Array.find(callbackFn)
Array.find(callbackFn, thisArg)

// Inline callback function
Array.find(function callbackFn(element) { conditional })
Array.find(function callbackFn(element, index) { conditional })
Array.find(function callbackFn(element, index, array){ conditional })
Array.find(function callbackFn(element, index, array) { conditional }, thisArg)
```

#### Examples
```js
const posts = [
  {
    id: 1,
    title: 'Hello World!'
  },
  {
    id: 2,
    title: 'Learn JS Deeply!'
  }
];

const post = posts.find(post => post.id === 2);
console.log(post); // {id: 2, title: "Learn JS Deeply!"}
```

### Array.findIndex()

The `Array.findIndex` static method returns the index of the first value in the provided array that satisfy the testing function or undefined if no elements match.

#### Syntax

```js
// Arrow function
Array.findIndex( element => { conditional } )
Array.findIndex((element, index) => { conditional } )
Array.findIndex((element, index, array) => { conditional } )

// Callback function
Array.findIndex(callbackFn)
Array.findIndex(callbackFn, thisArg)

// Inline callback function
Array.findIndex(function callbackFn(element) { conditional })
Array.findIndex(function callbackFn(element, index) { conditional })
Array.findIndex(function callbackFn(element, index, array){ conditional })
Array.findIndex(function callbackFn(element, index, array) { conditional }, thisArg)
```

#### Examples
```js
const posts = [
  {
    id: 1,
    title: 'Hello World!'
  },
  {
    id: 2,
    title: 'Learn JS Deeply!'
  }
];

const post = posts.findIndex(post => post.id === 2);
console.log(post); // 1 - Remember, this is zero based!
```

### Array.from()

The `Array.from` static method let's you create `Array`s from array-like objects and iterable objects.

#### Syntax
```js
// Simple without any mapping
Array.from(arrayLike);

// Arrow function mapping (Can't utilize the third argument for the `thisArg` argument since the mapping uses an arrow function)
Array.from(arrayLike, element => [...] );
Array.from(arrayLike, (element, index) => [...] );

// Mapping named function
Array.from(arrayLike, mapFn );
Array.from(arrayLike, mapFn, thisArg);
```

#### Examples

We all know that unfortunately we cannot just simply loop over a NodeList.

```js
const headers = document.querySelectorAll('h1');
const titles = headers.map(h1 => h1.textContent); // TypeError: headers.map is not a function
```

But, using `Array.from` we can loop over them easily!

```js
const headers = document.querySelectorAll('h1');
const headersArray = Array.from(headers);
const titles = headersArray.map(h1 => h1.textContent);

// Or we can use the `mapFn` parameter of `Array.from`.

const titles = Array.from(
  document.querySelectorAll('h1'),
  h1 => h1.textContent
);
```

### Array.indexOf()

The `Array.indexOf` static method returns the index at which passed in element can be found in an array.

#### Syntax
```js
Array.indexOf(searchElement)
Array.indexOf(searchElement, index)
```

#### Examples
```js
const values = Array.of(123, 456, 789);

console.log(values); // [123,456,789]
```

### Array.of()

The `Array.of` static method creates a new `Array` that consists of the values that are passed into it regardless of the type of or the number of arguments.

The big difference between this and the `Array` constructor method is how each handles single integer arguments.`Array.of(5)` will create an array with a single element but `Array(5)` creates an empty array with a length of 5.

#### Syntax
```js
Array.of(firstElement)
Array.of(firstElement, secondElement, ... , nthElement)
```

#### Examples
```js
const values = Array.of(123, 456, 789);
console.log(values); // [123,456,789]
```

## Promises

The Promise object is used for asynchronous operations and represents the eventual completion or failure of that operation, and its resulting value. <sup><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">1</a></sup>Promises are often used for fetching data asynchronously.

Promises exist in one of four states:

- **Pending** - When defined, this is the initial state of a Promise.
- **Fulfilled** - When the operation has completed successfully.
- **Rejected** - When the operation has failed.
- **Settled** - When it has either fulfilled or rejected.

```js
const promiseA = new Promise( ( resolve, reject ) => {
  setTimeout( () => {
    resolve('Resolved!');
  }, 300 );
} );
```
<img alt="Promise Harry Potter Gif" src="https://res.cloudinary.com/kevinlangleyjr-dev/image/upload/v1635020656/blog/modern-js/promise_z67bfm.gif">

### Promise API

There are 6 static methods in the Promise class:

#### `Promise.all`

The `.all()` method accepts an iterable of `Promise` objects as it's only parameter and returns a single `Promise` object that resolves to an array of the results of the `Promise` objects that were passed into it. The promise returned from this method will resolve when _all_ of the passed in promises resolve **or** will reject, if any of them rejected.

#### `Promise.allSettled`

The `.allSettled()` method accepts an iterable of `Promise` objects as it's only parameter and will return a `Promise` object that will resolve with an array of objects with the outcome of each `Promise` object when all of them have settled with either a resolve or reject. The biggest difference between `.allSettled()` and `.all()` is that the later resolves or rejects based on the outcomes of the passed in promises while the former will resolve once all `Promise` objects passed in are settled with either a `resolve` **or** `reject`.

#### `Promise.any`

The `.any()` method accepts an iterable of `Promise` objects as it's only parameter and will return a single `Promise` objet that resolves when any of the passed in `Promise` objects resolve, with the value of the resolved original `Promise` object.

#### `Promise.race`

The `.race()` method accepts an iterable of `Promise` objects as it's only parameter and will return a `Promise` object that resolves or rejects as soon as one of the passed in `Promise` objects resolves or rejects.

#### `Promise.resolve`

The `.resolve()` method returns a `Promise` object that is resolved with the value provided. If a `thenable` (has a `then` method) is the returned value, the returned `Promise` object will follow that `then` until the final value is returned.

#### `Promise.reject`

The `.reject()` method returns a `Promise` object that is rejected with the reason provided.

### Let's jump into some examples!

```js
const p1 = new Promise( (resolve, reject) => {
  resolve('Learn JavaScript Deeply!');
} );

p1.then(data => console.log(data)); // Learn JavaScript Deeply
```

#### Promises can even be chained!

```js
const p2 = new Promise( (resolve, reject) => {
  setTimeout( () => resolve( 1 ), 1000 ); // Resolve with `1` after 1 second.
} );

p2
  .then( value => {
    console.log( value ); // 1
    return value * 2;
  } )
  .then( value => {
    console.log( value ); // 2
    return value * 4;
  } )
  .then( value => {
    console.log( value ); // 8
    return value * 2;
  } );
```

#### Let's look at some more in depth examples.

Often, you will run into using promises when trying to fetch data.

```js
const postsPromise = fetch('https://miami.wordcamp.org/2018/wp-json/wp/v2/posts');

console.log(postsPromise);

// Promise {pending}
// __proto__: Promise
// [[PromiseStatus]]: "pending"
// [[PromiseValue]]: undefined

postsPromise
  .then(data => console.log(data));

// Response {type: "cors", url: "https://miami.wordcamp.org/2018/wp-json/wp/v2/posts", redirected: false, status: 200, ok: true, ...}

postsPromise
  .then(data => data.json()) // Get's JSON value from fetch.
  .then(data => console.log(data)) // Console log the value.
  .catch(err => console.error(err)); // Catch any errors.

// {id: 5060, date: "2018-03-15T17:41:09", ...}
// {id: 4954, date: "2018-03-14T00:21:10", ...}
// {id: 4943, date: "2018-03-13T19:16:11", ...}
// {id: 4702, date: "2018-03-10T11:04:36", ...}
// ...
```

You can catch errors that are thrown in promises using the `.catch` method as shown below.

```js
const p = new Promise((resolve, reject) => {
  reject(Error('Uh oh!'));
});

p.then(data => console.log(data)); // Uncaught (in promise) Error: Uh oh!

p
  .then(data => console.log(data))
  .catch(err => console.error(err)); // Catch the error!
```

## Classes

Behind the scenes, ES6 classes are not something that is radically new. They mainly provide more convenient syntax to create old-school constructor functions.

```js
// Class declaration
class Animal {

}

// Class expression
const Animal = class {}
```

```js
class Animal {

  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}

class Dog extends Animal {
  speak() {
    console.log(`${this.name} barks!`);
  }
}

const puppy = new Dog('Spot');
puppy.speak(); // Spot barks!
```

```js
class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    this.breed = breed;
  }

  speak() {
    console.log(`${this.name} barks!`);
  }
}
```

## Async/Await

The `await` keyword and `async` functions were added in ES2017 or ES8. They are really just syntactic sugar on top of Promises that help make asynchronous code easier and cleaner to read and write. The `async` keyword is added to functions to denote it will return a promise to be resolved, rather than directly returning the value.

Currently, you can only use the `await` keyword inside of an `async` function, but there is an open proposal for [top-level await](https://github.com/tc39/proposal-top-level-await).

Let's take a look at some promises and how we can convert them to async/await!

```js
const getUser = username => {
  return fetch(`https://api.github.com/users/${username}`)
  .then( response => {
    if ( ! response.ok ) {
      throw new Error( `HTTP error! Response status: ${response.status}` );
    }
    return response.json();
  } )
  .catch( e => console.error( e ) );
}

const user = getUser( 'kevinlangleyjr' );
console.log( user );
```

And now, let's turn change this to use async/await.

```js
const getUser = async username => {

  try {

    const response = await fetch(`https://api.github.com/users/${username}`)

    if ( ! response.ok ) {
      throw new Error( `HTTP error! Response status: ${response.status}` );
    }

    const user = await response.json();

    return user;

  } catch ( e ) {
    console.error( e );
  }
}

const user = getUser( 'kevinlangleyjr' );
console.log( user );
```


The difference being, we're now wrapping the entire function body where we do our `await`s in a `try/catch` block. This will catch any errors from the Promises that we are awaiting. We are also putting the `async` keyword in front of our function definition here as well, which is required for `await` to work properly.

## Set/WeakSet

A Set object allows you to store unique values of any type. While it is similar to an Array, there are a couple of big differences. The first being that an Array can have duplicate values and a Set cannot. And the other is that Arrays are ordered by index while Sets are only iterable in the order they were inserted.

You can capture the entries in the set using `.entries()` which will keep the reference to the original objects inserted.

Map and Set both have `keys()` and `values()` methods but on Sets, those methods will return the same iterable of values since Sets are not key value pairs but just values.

```js
const set = new Set();

set.add( 9 );      // Set(1) { 9 }
set.add( 9 );      // Set(1) { 9 } Ignored because it already exists in the set.
set.add( 7 );      // Set(2) { 9, 7 }
set.add( 7 );      // Set(2) { 9, 7 } Ignored because it already exists in the set.
set.add( 'Text' ); // Set(3) { 9, 7, 'Text' }
set.add( true );   // Set(4) { 9, 7, 'Text', true }

const user = {
  name: 'Kevin',
  location: 'Florida'
};

set.add( user );   // Set(5) { 9, 7, 'Text', true, { value: { location: 'Florida', name: 'Kevin' } } }

set.has( 9 );    // true
set.has( true ); // true

const newUser = {
  name: 'Kevin',
  location: 'Florida'
};

set.has( newUser );  // false - This doesn't do object property matching, it is an object reference check.

set.has( user );  // true

// Use .entries() to capture all the values in the set.

const entries = set.entries();

console.log( entries ); // SetIterator {9 => 9, 7 => 7, 'Text' => 'Text', true => true, {…} => {…}}

set.delete( true );  // true - This change is still reflected in entries because it keeps the reference

set.has( true );  // false

console.log( entries ); // SetIterator {9 => 9, 7 => 7, 'Text' => 'Text', {…} => {…}}

set.size // 5

// Iterating over a set is pretty simple but you have a few options.
for ( let item of set.values() ) {
  console.log( item );
}

// Is the same as,
for ( let item of set.keys() ) {
  console.log( item );
}

// And is also the same as,
for ( let [key, value] of set.entries() ) {
  console.log( key );
}
```

The main difference between a Set and a WeakSet is that the latter only accepts objects and the former can accept values of any type. The other big difference is that WeakSets hold weak references to the objects within them. So if there are no further references to an object that is within a WeakSet, those objects can be garbage collected. WeakSet's are also not enumerable nor is there a way to list the current objects within it, but you can `.add()`, `.delete()`, and check if a WeakSet `.has()` an object.

```js
let ws = new WeakSet();
let user = { name: 'Kevin', location: 'Florida' };
ws.add( user );
console.log( ws.has( user ) ); // true
user = null; // The reference to `user` within the WeakSet will be garbage collected shortly after this point.
```

If you were to use the above code sample and then force garbage collection in the Chrome dev-tools by clicking the trash can under the Performance tab, you'd see that `ws` no longer holds the reference to `user`.

<img alt="Force JavaScript garbage collection in Chrome" src="https://res.cloudinary.com/kevinlangleyjr-dev/image/upload/v1637687473/blog/modern-js/garbage-collection_u4khbx.png">

## Map/WeakMap

The Map object, on the other hand, hold key-value pairs, tracks the insertion order, and can use either objects or primitive values for both the key and value.

While Objects are very similar to Maps and historically have been used in place of Maps prior to their introduction in ES6, there are some key differences. Maps can have keys of any type of value, including functions and Objects, while Objects can only have Strings and Symbols as keys. You cannot directly iterate over an Object while there are several ways to iterate over a Map. Also, there is no native support for `JSON.stringify()`, but it is possible with a bit of [work](https://stackoverflow.com/questions/29085197/how-do-you-json-stringify-an-es6-map).

```js
const map = new Map();

map.set( 1, 'Learn' );// Map(1) {1 => 'Learn'}
map.set( 2, 'JavaScript' );// Map(2) {1 => 'Learn', 2 => 'JavaScript'}
map.set( 3, 'Deeply' );// Map(3) {1 => 'Learn', 2 => 'JavaScript', 3 => 'Deeply'}
map.set( 3, 'Very Deeply' );// Map(3) {1 => 'Learn', 2 => 'JavaScript', 3 => 'Very Deeply'} - Overwrite value of Deeply set on line above.

map.set(
  '1',
  'Different string value for the string key of 1.'
);  // Map(4) {1 => 'Learn', 2 => 'JavaScript', 3 => 'Very Deeply', '1' => 'Different string value for the string key of 1.'}

map.set(
  true,
  'Different string value for the bool of true.'
);  // Map(5) {1 => 'Learn', 2 => 'JavaScript', 3 => 'Very Deeply', '1' => 'Different string value for the string key of 1.', true => 'Different string value for the bool of true.'}

map.size // 5

map.delete( true );  // Map(4) {1 => 'Learn', 2 => 'JavaScript', 3 => 'Very Deeply', '1' => 'Different string value for the string key of 1.'}

map.delete( '1' );  // Map(3) {1 => 'Learn', 2 => 'JavaScript', 3 => 'Very Deeply'}

map.clear();  // Map(0) {size: 0}

/* New example with objects for keys. */

const user1 = { name: 'James' };
const user2 = { name: 'Kevin' };

// New Map for votes.
const votes = new Map();
votes.set( user1, 10 );  // Map(1) {{ name: 'James' } => 10}
votes.has( user1 );  // true
votes.has( user2 );  // false - Not set yet.
votes.set( user2, 20 );  // Map(2) {{ name: 'James' } => 10, { name: 'Kevin' } => 20}

votes.has( user1 );  // true
votes.has( user2 );  // true

votes.get( user1 );  // 10
votes.get( user2 );  // 20

votes.size // 2
```

## References

1. [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)