ES6+ (ESNext)

EXTRAVAGANZA!!

What is ES6+

  • New features for the language
  • ESNext is the latest version
  • It's now intended to roll new features out continuously in sets
  • TC39 is in charge of the process
  • There are several stages that a feature goes through before it's accepted
    • Full overview of the process: here
  • List of features (and where they're up to): here

Features list:

  • modules
  • let
  • const
  • Arrow functions
  • Default Parameters
  • Template strings
  • New string functions
  • De-structuring
  • Array improvements
  • Spread and rest
  • Object Literal Upgrades
  • for...of loop
  • Promises (native)
  • Symbol
  • Classes
  • Generators
  • Proxies
  • sets & weaksets
  • maps & weakmaps
  • async, await (es7)

Can I use them now?

https://kangax.github.io/compat-table/es6/

 

Hmmm, what do I do about older browsers...

Polyfills

  • A polyfill is a piece of code you load to cover the shortfalls in a browser's capabilities
  • Say safari doesn't support a new array method yet, you can probably find a polyfill for that which loads if it is not supported
  • Famous libs include:

Transpilers

Modules

// ES5 export

module.exports = function(){
 ...
};

exports.data = [{..}, {..}];

exports.isDeveloper = false;

// ES5 Import
var myProgram = require('./myJS');
var data = require('./myJS').data;






// ES6 export
export const MONTH = 'January';
export const DAY = 'Friday';

export default myFunc = function(){..};

// ES6 import
import passport from 'passport';
import myScript, { MONTH } from './myEs6';

const

const str = 'phil';
const str = 'dave'; // error - Can't redeclare
str = 'tom'; // error - Can't re-assign

const arr = [0,1,2];
arr.push(4); // works

const tutor = {
    name: 'james'
};

tutor.name = 'rich'; // works
tutor = {
    name: 'seymour'
}; // error - Can't re-assign



const is a constant which means that it cannot be re-declared or re-assigned

So, once assigned, an object can't be reassigned BUT its properties can be changed. (See here to stop that from happening)

let

let str = 'phil';
let str = 'dave'; // error - Can't redeclare
str = 'tom'; // works

let is a variable. It cannot be re-declared but it can be re-assigned.

let is like var but 'block scoped' (So not hoisted!)

Temporal Dead Zone

function old(){
    console.log(thing); // undefined
    var thing = 'panda';
}

function new(){
    console.log(thing); // ERROR
    console.log(otherThing); // ERROR
    let thing = 'panda';
    const otherThing = 5;
}

You cannot access a let or const before it is defined - they are 'block scoped' not 'function scoped', so they are not hoisted.

Is var dead?

(Fat) Arrow Functions

// Multi params need ()s
(a, b) => {
    return a + b;
}

// Single parameter
a => {
    return a * 2;
}

// No params
() => {
    return 'hello!';
}

// Implicit return
a => a * 2 // the product of a * 2 is implicitly returned

// Implicit return of an object
guest => ({ name: guest.name })

// Currying
let add = x => y => x + y;

//^^ Equivalent
let add = function (x) {
  return function (y) {
    return x + y;
  };
};
  • Does not have its own this (takes from parent context)
  • Don't use as methods!! (when not to use)
  • Don't use for event handlers if this for element is necessary
  • They are anonymous
  • They have no arguments object
  • various ways to declare
  • all have =>

Default Function Parameters

function getThemeDetails(textColor = '#0f0', textSize = 16){
    // ...
}
  • Can be used with any type of function
  • They provide a default value when an argument is not passed
  • format paramName = defaultValue

Template Strings

const name = 'phil';
const age = 24;
const sentence = `${name} is ${age} years old`;
  • Like templating libraries
  • uses `` to mark start and end of string
  • uses ${} to mark insertion areas
  • What you insert must be coerce-able into a string

Advanced Template Strings

const friends = [{name: 'tom', age: 27}, {name: 'geoff', age: 43}];
const sentence = `
    <ul class="friends">
        ${friends.map(friend => `<li>${friend.name} is ${friend.age} years old</li>`).join('')}
    </ul>
`;

You can execute javascript inside the ${} but it must be coerce-able into a string

You can manufacture HTML in the insertion points

const name = 'fido';
const age = 24;
const sentence = `${name} is ${ age * 7 } years old (in dog years)`;

Or you can use a function:

function createMarkup(){
    let markup = ``;
    if(....) {....} else {....}
    return markup;
}

const sidebar = `<aside id="sidebar>${createMarkup()}</aside>`

Tagged Template Strings

  • You can run a template string through a function
  • Gives you:
    • the string fragments back as an array
    • Any variables that you would have used

New String Methods

const insurance_number = "JW735025B";
insurance_number.endsWith('B'); // true
insurance_number.endsWith('25', insurance_number.length - 1); // true
.startsWith(searchString[, startPosition]);
.endsWith(searchString[, length]);
const insurance_number = "JW735025B";
const international_insurance_number = "GBJW735025B";
let isNI_Number = insurance_number.startsWith('JW'); // true
let isNI_Number2 = international_insurance_number.startsWith('JW'); //false
let isINI_Number = international_insurance_number.startsWith('JW', 2); //true
.includes(searchString[, startPosition]);
'Blue Whale'.includes('blue'); // false Case sensitivity
'es6-es6-next'.includes('es6', 3); // true
.repeat(numberOfTimes);
'abc'.repeat(2);    // 'abcabc'

(Position is optional, zero-based and defaults to 0)

New String Methods

const greeting = '   Hello world!   ';

console.log(greeting);
// expected output: "   Hello world!   ";

console.log(greeting.trimEnd());
// expected output: "   Hello world!";
.padStart/End(totalCharLength[, padding char]);
.trim[Start/End]();
const str1 = 'Breaded Mushrooms';

console.log(str1.padEnd(25, '.'));
// expected output: "Breaded Mushrooms........"

const str2 = '200';

console.log(str2.padEnd(5));
// expected output: "200  "

BIG ONE: Destructuring

var person = {
    name: 'James',
    age: 39,
    social: {
        twitter: 'jmsherry',
        facebook: 'panda.sherry'
    },
};

// Old way
var name = person.name;
var age = person.age

// New way
const { name, age } = person; // Now 'name' and 'age' exist as variables

// Old way
const { twitter, facebook } = person.social; // as do 'facebook' and 'twitter'
// New way
const { name, social: { twitter, facebook } } = person;

Destructuring cont...

var person = {
    name: 'James',
    age: 39,
    social: {
        twitter: 'jmsherry',
        facebook: 'panda.sherry'
    }
};

const { twitter:tw, facebook:fb } = person.social; 

unpack into different variable names

format is originalName: newName

now 'fb' and 'tw' hold the twitter and facebook variables

Destructuring cont...

var settings = {
    width: 300,
    height: 200,
};

const { width:w = '100%', height:h = '200', color = 'blue' } = settings; 

w/ default arguments

format is value = default

Destructuring Arrays

const friends = ['tom', 'dave', 'hannah'];
const [friend1, friend2, friend3] = friends;
// Now friend1 is available as a variable and
// has the value 'tom'
// 
// Is the equivalent of:
// const friend1 = friends[0];



const sports = 'basketball, baseball, football';
const [sport1, sport2, sport3] = sports.split(', ');

Destructuring Parameters

function announcePerson({name, rank}) {
  console.log(`Introducing ${rank} ${name}`);
}

const person = {
  rank: 'Lieutenant',
  name: 'James Sherry',
};

announcePerson(person)

  • This allows us to do 'named arguments'
    • This is a lot better than arguments-by-order because you can add new properties and not have to worry about injection order

Array improvements

Instance Methods

Which means they are found 'inside' each array you create

New Methods

Static Methods

Array.from()

Create an array from any iterable thing (array, string, map, etc.)

const newArr = Array.from('foo'); 
console.log(newArr); // ["f", "o", "o"]

// OR

function adder(){
    const numbers = Array.from(arguments);
    const total = numbers.reduce((accumulator, currentValue) => accumulator + currentValue);
    return total;
}

adder(1, 34, 98, 65, 23);

Array.isArray()

Array.isArray([1, 2, 3]);  // true
Array.isArray({foo: 123}); // false
Array.isArray('foobar');   // false
Array.isArray(undefined);  // false

Tests if something is an array (because typeof [] gives 'object')

Array.of()

Array(1,2,3); // [1,2,3];

Array(3); // [<empty slot>, <empty slot>, <empty slot>]

Array.of(3); //[3]

Because the Array() constructor has a fault

Object Literal Upgrades

object property shorthand

function getPerson(name, age){
    return {
        name: name,
        age: age
    };
}

// becomes

function getPerson(name, age){
    return {
        name,
        age,
    };
}

If the key matches the variable name then write it once

dynamic keys

const VIP_SUFFIX = 'VIP';
const KEY = 'CLIENT';
const name  = 'John'

let person = {}
person[`${KEY}-${VIP_SUFFIX}`] = name;

// or
person = {
  [`${KEY}-${VIP_SUFFIX}`]: name;
}

Dynamically calculated things are often called: 'computed values'

object method shorthand

function getPerson(name, age){
    return{
        start: function(){....},
        stop: function(){....}
    };
}

// becomes

function getPerson(name, age){
    return{
        start(){....},
        stop(){....}
    };
}

BIG ONE:
rest and spread

IDEA: gather or unpack

  • The rest operator gathers 'incoming'
  • The spread operator spreads 'outgoing'
  • SAME SYNTAX: ...identifier

rest operator

const firecrew = ['hannah', 'dave', 'kamarjit', 'paul', 'tom'];
const [CO, XO, ...crew] = firecrew; 

// CO: hannah
// XO: dave
// crew: ['kamarjit', 'paul', 'tom']

The rest operator gathers things under one variable name

spread operator

var arr1 = [0,1,2];
var arr2 = [3,4,5];
var arr3 = arr1.concat(arr2);

// becomes
const arr3 = [...arr1, ...arr2];
const arr4 = [...arr1, 'some other value', ...arr2];

for...of loop

Details

  • Loops over anything which is iterable
  • If you console.log something, and in the __proto__ it has a Symbol.iterator, then it is iterable (see below)

fig. 1: An iterable

Why necessary?

Let's think about other types of loop that we have:

  • for loop
    • not easy syntax
    • does not read that fluidly
    • no closure if you use var not let
  • .forEach(fn)
    • no ability to break
    • no ability to continue
  • for...in
    • If someone has added a method
    • or added things to the prototype, these extra things get iterated over!! (May happen with 3rd party libraries or polyfills)

Syntax

const cuts = ['Middle', 'Back', 'Loin'];

for (const cut of cuts) {
    console.log(cut);
}

// 'Middle', 'Back', 'Loin'

for (const [index, cut] of cuts.entries()) {
    console.log(index, cut);
}

// 0, 'Middle', 1, 'Back', 2,  'Loin
// 
const obj = { name: 'james', age: 43 };

for(const [key, value] of Object.entries(obj)) {
  console.log(`key: ${key}, value: ${value}`);
}
  1. You need const before cut, or you'll create global leakage. 
  2. continue and break work
  3. .entries() gives access to the array iterator
  4. cuts.entries() gives [0,'Middle'], [1, 'Back'], [2, 'Loin']
  5. with array deconstruction, we can then split those out!
  6. We can use on objects

Symbol

What are they used for?

  • A symbol is used when you want to create something unique
  • It is unique throughout the entire system
  • It can be created on the spot with Symbol(<identifier string>);
  • Or it can be set or retrieved into a global registry via the Symbol.for() and Symbol.keyFor() methods (demo)
  • Iteration over an object with symbols in will not show them unless you use Object.getOwnPropertySymbols(obj), so they're good for privacy
  • Even symbols created with the same identifier are not equal
  • Two common use cases are:
    • unique property keys
    • constants representing concepts

Examples

unique property keys

const classroom = {
    mary: {scores: [90, 70]},
    john: {scores: [80, 60]},
    mary: {scores: [85, 70]} // ERROR: Duplicate key
};

// vs

const classroom = {
    [Symbol('mary')]: {scores: [90, 70]},
    [Symbol('john')]: {scores: [80, 60]},
    [Symbol('mary')]: {scores: [90, 70]} // No error
};

Constants representing concepts

const COLOR_RED    = 'Red';
const COLOR_ORANGE = 'Orange';
const COLOR_YELLOW = 'Yellow';
const COLOR_GREEN  = 'Green';
const COLOR_BLUE   = 'Blue';
const COLOR_VIOLET = 'Violet';

function getComplement(color) {
    switch (color) {
        case COLOR_RED:
            return COLOR_GREEN;
        case COLOR_ORANGE:
            return COLOR_BLUE;
        case COLOR_YELLOW:
            return COLOR_VIOLET;
        case COLOR_GREEN:
            return COLOR_RED;
        case COLOR_BLUE:
            return COLOR_ORANGE;
        case COLOR_VIOLET:
            return COLOR_YELLOW;
        default:
            throw new Exception('Unknown color: '+color);
    }
}

// Prone to mix ups because of case and non-uniqueness. What if I have a:
const MOOD_BLUE = 'Blue';

// Better would be to use
const COLOR_RED    = Symbol('Red');
const COLOR_ORANGE = Symbol('Orange');
const COLOR_YELLOW = Symbol('Yellow');
const COLOR_GREEN  = Symbol('Green');
const COLOR_BLUE   = Symbol('Blue');
const COLOR_VIOLET = Symbol('Violet');

See here for more

Classes

Classes

  • This is syntactic sugar over the pseudo-classical pattern
  • Remember: A Class is like a mould for something. That something is an instance, which in JavaScript is an object.
  • So, Classes are used to create objects
  • Declared like so:
class Dog {} // hoisted

// or 

const Dog = class {} // not hoisted

Pseudo-classical vs Class syntax

class Dog {
    constructor(name, breed){ // required
        this.name = name;
        this.breed = breed;
    }
    // class method. Available on each object
    speak(){
        return `Bark! My name is ${this.name}!!`;
    }
    // static method. Available on class only
    static getAgeinDogYears(age){
        return age * 7;
    }
    set nickname(value) {
        this.nick = value.trim();
    }
    get nickname() {
        return this.nick;
    }
}
function Dog(name, breed){
    var _licenceNo = getLicenceNumber(); // private var
    this.name = name; // public var
    this.breed = breed
    this.licenceNumber = function() { // privileged method
        return _licenceNo;
    }
}

Dog.prototype.speak = function(){ // public method
    return `Bark! My name is ${this.name}!!`;
};

Dog.getAgeInDogYears = function(age){ // static method
    return age * 7;
};

Dog.isDog = function(thing){....} // static

Note: No commas between methods ^^

Class inheritance (extends)

class Animal {
    constructor(name, noise){ // required
        this.name = name;
        this.thirst = 100;
        this.belly = [];
        this.noise = noise;
    }
    drink(){
        this.thirst -= 10;
    }
    eat(item){
        this.belly.push(item);
        console.log(this.belly);
    }
    speak() {
        return this.noise
    }
}

class Dog extends Animal {
    constructor(name, noise, breed){
        super(name, noise);
        this.breed = breed;
    }
    speak() {
        return `${this.noise} - I am a dog!`;
    }
    fetch() {
        console.log('Here\'s your stick back!');
    }
}
  • You must execute the constructor of the class you are inheriting from and pass that constructor any info required before use of 'this': super(arg);
  • Any method you specify in the sub class will override a method of the same name from the super class
const bear = new Animal('Boris', 'Grrr');
const fido = new Dog('Fido', 'Woof', 'spaniel');

Fido.fetch(); // Here's your stick back
Fido.eat('plastic');
bear.fetch(); // ERROR
bear.eat('dog'); // ['dog']
bear.speak(); // 'Grrr'
fido.speak(); // 'Woof I am a dog'

Class public, private & static members

class Bike {
 constructor(name) { this.name = name }
}

// Referenced from class (static)
Bike.shippingContainerType = 4; 

// Every object gets a copy (public)
Bike.prototype.something = 25;


class MotorBike extends Bike {
  	licenceType = 1; // public
  	engine; // public

    static getReg = function(){} //static

	#privateThing = 2; // private
    constructor(name, licenceType, priv){
        super(name);
        this.engine = {};
      	this.licenceType = licenceType;
      	this.privateThing = priv;
    }
  	
    drive() {
        return `I'm driving the ${this.name}`;
    }
}
  • You can set up a class to use methods that don't have to be created when the object is constructed
    • there's the old way
    • and new way (with babel)
  • public methods are available on each object (myArray.forEach)
  • private are only usable by methods inside the class
  • static are utility methods stored in the class (Array.from)

Extension of Existing Features

class MusicCollection extends Array {
    constructor(title, ...tracks){
        super(tracks);
        this.title = title;
    }
    add(track){
        this.push(track);
    }
}

const musicCollection = new MusicCollection('James\'s Music', {
    band: 'Drowning Pool',
    track: 'Bodies'
}, {
    band: 'Machine Head',
    track: 'Davidian'
}, {
    band: 'Filter & Crystal Method',
    track: 'Trip Like I Do'
}, {
    band: 'Devin Townsend Project',
    track: 'The Death of Music'
});

Iterators & Generators

An Iterator

  • docs
  • demo
  • Iterators are objects that have a .next() method we call to get the next thing from them.
  • MANY different things can use them, like spread operators (above), or for....of loops, etc.
const myIterable = {};
myIterable[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};

const iterator = myIterable[Symbol.iterator]();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

console.log([...myIterable]); // [1, 2, 3]

for (const item of myIterable) {
  console.log("item", item);
}

What is a generator function? 

  • A function that returns an iterator. It's how we easily produce them...
  • A normal function in JavaScript runs from top to bottom and then exits returning one product
  • With a generator you can start and stop the function
  • You can have multiple return values (i.e. the function returns several times)
  • Generators are identifiable by
    • the asterisk (*) after the function keyword
    • the use of the word yield within the function body

A simple example

  • You can pass values to next to be used in calculating the next response - they will be returned by the yield statement
  • If you wanted to yield the result of calling another generator, then use yield*
  • For a sugared example, wait for async/await at the end of this presentation
  • Guide
  • demo (passing arguments, async)
function* myGenerator(name){
    yield 'James';
    yield 'Sherry';
}

const iterator = myGenerator();
iterator.next(); // {value: 'James', done: false}
iterator.next(); // {value: 'Sherry', done: false}
iterator.next(); // {value: undefined, done: true}

Proxies

What & Why?

  • A proxy, in all cases (e.g. proxy server) is something that you put between the user and the original object/server
  • It allows you to customise the interaction
  • Would work well if enhancing interaction with an already established system

How to use a Proxy

  • Use the Proxy() constructor
  • 2 arguments
    1. target object that you're proxying
    2. a set of handlers (get/set overrides for your object) (See here)
let validator = {
  set: function(obj, prop, value) {
    if (prop === 'age') {
      if (!Number.isInteger(value)) {
        throw new TypeError('The age is not an integer');
      }
      if (value > 200) {
        throw new RangeError('The age seems invalid');
      }
    }

    // The default behavior to store the value
    obj[prop] = value;

    // Indicate success
    return true;
  }
};

let person = new Proxy({}, validator);

person.age = 100;
console.log(person.age); // 100
person.age = 'young'; // Throws an exception
person.age = 300; // Throws an exception

Pen

set & weaksets

What is a set?

  • A set is like an array
  • BUT, it can only have unique values in it
const colourArray = ['red', 'red', 'orange', 'blue'];
const colourSet = new Set(colourArray);

console.log(colourArray); // ['red', 'red', 'orange', 'blue']
console.log(colourSet); // ['red', 'orange', 'blue']
  • What you pass to the constructor must be an iterable
  • Sets have a size property (analogous to an array's length property)

set methods

  • .add(value)
const colourSet = new Set(['red', 'orange', 'blue']);
colourSet.add('pink');
console.log(colourSet); // ['red', 'orange', 'blue', 'pink']
  • .has(value)
colourSet.has('pink'); // true
  • .delete(value)
  • .clear()
colourSet.delete('pink'); // true
console.log(colourSet) // ['red', 'orange', 'blue']
colourSet.clear(); // true
console.log(colourSet) // Set(0) {}

set methods cont...

  • .entries()
  • .keys()
  • .values()

...get you an iterator (See generator functions) to use with for...of loops

What is 'weakness'?

In JavaScript any reference to an object means that that object cannot be garbage collected. 'Weak' structures take a reference to an object in such a way that its presence in that structure doesn't stop it being garbage collected if there are no other references remaining.

 

demo

weakset

  • weaksets contain objects only
  • They have the following methods
    • .add(obj), .has(obj), .delete(obj)
    • NO .clear() or .size
  • Cannot iterate over a weakset
  • Garbage collection is not instantaneous
var ws = new WeakSet();
var obj = {};
var foo = {};

ws.add(window);
ws.add(obj);

ws.has(window); // true
ws.has(foo);    // false, foo has not been added to the set

ws.delete(window); // removes window from the set
ws.has(window);    // false, window has been removed

maps & weakmaps

What is a map?

  • A map is like an object
  • BUT, its keys can be ANYTHING!!
  • object vs map

When should I use a map?

If you answer 'yes' to any of the following questions:

  • Are keys usually unknown until run time? Do you need to look them up dynamically?
  • Do all values have the same type? Can they be used interchangeably?
  • Do you need keys that aren't strings?
  • Are key-value pairs often added or removed?
  • Do you have an arbitrary (easily changing) amount of key-value pairs?
  • Does the collection need to be iterated over?

How to use a map?

map methods

  • .set(key, value)
const myMap = new Map();
myMap.set(2, 'pink');
console.log(myMap); // Map(1) {2 => "pink"}
  • .has(key)
console.log(myMap.has(2)); // true
  • .delete(key)
  • .clear()
myMap.delete('bar'); // True if found and removed; false if not
var myMap = new Map();
myMap.clear();
console.log(myMap); // Map(0){}
  • .get(key)
console.log(myMap.get(2)); // 'pink'
  • .size <-- property!
console.log(myMap.size); // 1

Other map methods

  • .forEach()
myMap.forEach(function logMapElements(value, key, map) {
    console.log(`m[${key}] = ${value}`);
});

.entries(), .keys(), .values() get you an iterator

weakmap

  • weakmap keys must be objects
  • They have the following methods
    • .get(obj), .set(obj), .has(obj), .delete(obj)
    • NO .clear() or .size
  • They are weakly held, so if there are no other references to an item in that weakmap, then it is garbage collected
  • Cannot iterate over a weakmap
  • Garbage collection is not instantaneous
const clickCount = new WeakMap();
let btns = document.getElementsByTagName('button');
btns.forEach((btn) => {
    clickCount.set(btn, 0);
    btn.addEventListener('click', (e) => {
        const count = clickCount.get(e.target);
        clickCount.set(e.target, count+=1);
    });
});

Promises

Skip until ajax

Creating a promise

  • A promise is a proxy for an object not yet attained
  • A promise has two states
    • pending
    • settled
  • and two outcomes
    • ​resolved
    • rejected
  • ​Promises are not terminable.
  • There is a definite future.
  • You construct a promise, passing it an executor function that decides resolution or rejection
var isMomHappy = false;

// Promise
var willIGetNewPhone = new Promise(
    function (resolve, reject) {
        if (isMomHappy) {
            var phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone); // fulfilled
        } else {
            var reason = new Error('mom is not happy');
            reject(reason); // reject
        }

    }
);

//Format: var myPromise = new Promise(executor);

Consuming a promise

  • Once you've created a promise you can load handlers into it that deal with what to do with the successful outcome and/or how to handle the error
  • You can load many of these 'after the fact'
  • REMEMBER: Promises are ASYNCHRONOUS
var askMom = function () {
    console.log('start');
    willIGetNewPhone
        .then(function (fulfilled) {
            // yay, you got a new phone
            console.log('middle', fulfilled);
         // output: { brand: 'Samsung', color: 'black' }
        })
        .catch(function (error) {
            // oops, mom don't buy it
            console.log(error.message);
         // output: 'mom is not happy'
        });
    console.log('end');
};

askMom();

/* You'll get: start, end, middle {result}

Bluebird

async & await

An enhancement on promises

/* ES7 */
const isMomHappy = true;

// Promise
const willIGetNewPhone = new Promise(
    (resolve, reject) => {
        if (isMomHappy) {
            const phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone);
        } else {
            const reason = new Error('mom is not happy');
            reject(reason);
        }

    }
);

// 2nd promise
async function showOff(phone) {
    return new Promise(
        (resolve, reject) => {
            const message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';

            resolve(message);
        }
    );
};


// call our promise
async function askMom() {
    try {
        console.log('before asking Mom');

        const phone = await willIGetNewPhone;
        const message = await showOff(phone);

        console.log(message);
        console.log('after asking mom');
    }
    catch (error) {
        console.log(error.message);
    }
}

(async () => {
    await askMom();
})();

Full Guide

Up to 2018

2019 & 2020

optional chaining, nullish coalescence, BigInt

Optional Chaining

  • Getting properties from an object can be risky
  • What if that method doesn't exist
  • What if the structure you're expecting isn't there?
  • The optional chaining operator (?.) takes care of this
    • it replaces things like obj && obj.method();
const adventurer = {
  name: 'Alice',
  cat: {
    name: 'Dinah'
  }
};


const dogName = adventurer.dog.name; // Throws: "Cannot read properties of undefined (reading 'name')"
const value = adventurer.someNonExistentMethod(); // Throws: "adventurer.someNonExistentMethod is not a function"

const dogName = adventurer?.dog?.name;
console.log(dogName);
// expected output: undefined

console.log(adventurer.someNonExistentMethod?.());
// expected output: undefined

nullish coalescene

  • We can set defaults using || BUT some values can be tricky for us, e.g. 0 or "" or false
  • The nullish coalescene operator (??) takes care of this
    • it replaces things like const _thing = param || 'thing';
// if undefinedValue is undefined, result: 'some other default'
const undefinedValue = response.settings.undefinedValue ?? 'some other default'; 

// if nullValue is null, result: 'some other default'
const nullValue = response.settings.nullValue ?? 'some other default'; 

// result: '': This would default with || because '' is falsey
const headerText = response.settings.headerText ?? 'Hello, world!'; 

// result: 0: This would default with || because 0 is falsey
const animationDuration = response.settings.animationDuration ?? 300;

// result: : This would default with || because false is falsey
const showSplashScreen = response.settings.showSplashScreen ?? true; 
  • Shorcuts like ??= and ||= can be found here

2021 & 2022

  • Promise.any, Numeric separators, String replaceAll, WeakRef, Finalizers
  • Logical assignment operator
    • And & Equals (&&=)
    • OR & Equals (||=)
    • Nullish Coalescing & Equals (??=)

2023

  • Top-level await
  • Private instance fields, methods, and accessors
  • Static class fields and methods
  • Static class initialization blocks
  • Error: .cause
  • Array, String, and TypedArray: .at() Method
  • Object: .hasOwn()
  • RegExp: match .indices ('d' flag)

JS: ES6 EXTRAVAGANZA!!

By James Sherry

JS: ES6 EXTRAVAGANZA!!

Intro to ES6/7

  • 2,140