ES Next - patrickcole/learning GitHub Wiki

ES-Next

ECMAScript Next as of 2020 (ES11)

NOTE: These are just my raw notes, much of the text here will probably look like it was copied directly from sources. In many cases, I did jot down exactly what the author stated, but I do not claim this text as my own

Many of the features on this page have yet to be fully implemented into modern browsers. Utilizing tools such as Babel will allow developers to start using these new features as Babel will transpile the code down to modern browser equivalent.

The problem is essentially trying to access a property that does not exist when the code is executed. Optional chaining solves this by allowing the execution to stop when it hits an optional parameter if the value is not found. Therefore, JavaScript will no longer throw an error. Combine this with another new feature Nullish Coalescing and a default value could be applied when a value is not found.

A sample is provided below:

const flower = {
  colors: {
    red: true
  }
}

console.log(flower.colors.red); // =>  true
console.log(flower.species.lily); // => TypeError: Cannot read property 'lily' of undefined

// now with optional chaining, we can check for the existance of the species property:
console.log(flower.species?.lily); // => undefined

Optional chaining can also be used in arrays as well as functions to check for existence.

let flowers = ['lily','daisy'];
console.log(flowers[1]); // => 'lily'
flowers = null;
console.log(flowers[1]); // => TypeError: Cannot read property '1' of null

// now with optional chaining:
console.log(flowers?.[1]); // => undefined

The same is true for a function:

let plantFlowers = _ => 'orchids';
console.log(plantFlowers()); // => 'orchids'

// check for a login function using optional chaining:
console.log(login?.()); // => undefined

Typically to provide a default value for a variable, an OR-statement would be used:

let number = 1;
let myNumber = number || 7;
console.log(myNumber); // => 1

However, if the left-side value happens to be a falsy value, such as 0, then it will ignore that value and use the right-hand argument. This is problematic as we may actually want to use the 0 in the statement:

let number = 0;
let myNumber = number || 7;
console.log(myNumber); // => 7

This is where nullish coalescing can come in handy. By changing the syntax to use double question marks ?? instead of an OR-statement (||) the left hand value can be falsy and it will still be applied:

let number = 0;
let myNumber = number ?? 7;
console.log(myNumber); // => 0

// however, what if this occurred:
let newNumber = exampleNumber ?? 7;
console.log(newNumber); // => 7

The use of private fields really relates to the implementation of Class in JavaScript. Private fields can only be accessed from within their respective class. This is different from public fields, which can be accessed from outside their class and protected fields which are only accessible from sub-classes.

The syntax for denoting a private field in ES11 is the use of a hash symbol #:

class Flower {
  #leaf_color = "green";
  constructor(name) {
    this._name = name;
  }
  get_color() {
    return this.#leaf_color;
  }

  get name() {
    return this._name;
  }

  set name(newName) {
    this._name = newName;
  }
}

const orchid = new Flower("orchid");

console.log(orchid.get_color()); // => "green"
console.log(orchid.#leaf_color); // => Error: Private name #leaf_color is not defined

Prior to static fields, a class would have to be instantiated first.

class Flower {
  add_leaves() {
    console.log(`Adding leaves`);
  }
}

const rose = new Flower();
rose.add_leaves();
// => 'Adding leaves'

Flower.add_leaves(); // => TypeError: Flower.add_leaves is not a function

Now, with the addition of the static field, a method can be invoked without the class being instantiated:

class Flower {
  constructor(type) {
    this._type = type;
  }
  static create_flower(type) {
    return new Flower(type);
  }
}

const rose = Flower.create_flower("rose");

Top Level Await

Async/Await brought a pattern that made async code appear more synchronous. However, one limit to this was that all code marked with an await flag would need an outer async function. For global scope calls, this meant that the code would have to be wrapped in an immediately invoked function expression (IIFE)

(async () => {
  const response = await fetch(url);
})();

Top Level Await now permits await calls inside the global scope.

const response = await fetch(url);

Promise.allSettled

Promise.all[] is a great choice to wait for all promises to be completed. However, if at least one of the promises included in the array fails, then an error will be thrown and other pending promises will cease.

What Promise.allSettled adds is the ability for any promise to fail and the other promises will still resolve.

const p1 = Promise.resolve('hello')
const p2 = new Promise((resolve, reject) => setTimeout(reject, 200, 'problem'))

Promise.allSettled([p1, p2])
  .then(([p1r, p2r]) => {
    console.log(p1r) // => {status: 'fulfilled', value: 'hello'}
    console.log(p2r) // => {status: 'rejected', reason: 'problem'}
  })

Dynamic Import

I've got a page for this and will be migrating this content over once it's fully implemented.

This pattern has been around since Webpack introduced the idea of importing code for bundles. This is also essential in the idea of code splitting. Rather than having to provide all code up-front, code can be loaded in on-demand via Dynamic Imports. Now ES11 adds this feature natively.

Alert.js

export default {
  show() {
    return `Your application is ready!`
  }
}

app.js

import('/components/Alert.js')
  .then(Alert => {
    Alert.show()
  })

Here's another example, with a little more context (derived from Freecodecamp.org example):

if ( condition ) {
  const module = await import('./Number.js')
  module.addNumbers(3,4,5);
}

Useful for RegularExpressions (RegEx) matching on a string to find all matches and return back positions:

const regex = /\b(apple)+\b/;
const fruits = "pear, apple, banana, apple, orange, apple";

for ( const match of fruits.match(regex)) {
  console.log(match);
}

// =>
// 'apple'
// 'apple'
// 'apple'

Now with matchAll, more detailed information is returned:

const regex = /\b(apple)+\b/g;
const fruits = "pear, apple, banana, apple, orange, apple";
for (const match of fruits.matchAll(regex)) {
  console.log(match);
}

// =>
// [
//   'apple',
//   'apple',
//   index: 6,
//   input: 'pear, apple, banana, apple, orange, apple',
//   groups: undefined
// ],
// [
//   'apple',
//   'apple',
//   index: 21,
//   input: 'pear, apple, banana, apple, orange, apple',
//   groups: undefined
// ],
// [
//   'apple',
//   'apple',
//   index: 36,
//   input: 'pear, apple, banana, apple, orange, apple',
//   groups: undefined
// ]

globalThis

This, no pun intended, is to assist with development of JavaScript in multiple runtime environments. For example, JavaScript run in NodeJs has a different global object than JS run in the browser (the window).

By using globalThis to match either the window or global object, developers can adjust their code accordingly.

Increases the amount of numbers available in JavaScript.

The previous max limit was at 253 - 1

Note: Numbers that go beyond the previous max number will need to have a n appended for the JS engine to recognize them as a BigInt

Module Namespace Exports

Previously, namespaces could be assigned on the import statement with JavaScript Modules. Now, namespaces can be assigned on the export statement as well:

export * as utils from './utils.mjs';

Import Meta Information

<script type="module" src="module.js"></script>
console.log(import.meta);
// => { url: "file:///home/user/module.js" }

Native FileSystem API

  • opens up the ability to interact with user's filesystem
  • obvious security concerns are in discussion/planning
  • must be granted permission to access files
  • system files and folders are not allowed to be accessed

Numeric Seperators

  • the use of an _ underscore can now be used as a numeric separator
console.log( 1_000_000_000_000 ); // => 1,000,000,000,000
console.log( 1_050.95 ); // => 1,050.95
let amount = 39_99;
console.log(amount); // => 3999

Sources

⚠️ **GitHub.com Fallback** ⚠️