So, what’s new in ES2020?

A handpicked selection of cool stuff by Christophe Porteneuve at Confoo Montréal 2018
https://joind.in/talk/e8ec5

whoami


const christophe = {
  family: { wife: 'Élodie', son: 'Maxence', [Symbol.guess]: '*Shh*' },
  city: 'Paris, FR',
  company: 'Delicious Insights',
  firstTimeInMontreal: true,
  lastTimeInMontreal: Symbol.noWay,
  trainings: [
    'Web Apps Modernes', 'Node.js', 'Git Total',
    'ES Total', 'Webpack'
  ],
  webSince: 1995,
  claimsToFame: [
    'Prototype.js',
    'Ruby On Rails',
    'Prototype and Script.aculo.us',
    'Paris Web',
    'NodeSchool Paris'
  ],
}
          

ES2020?!

ECMA, TC39, ECMAScript and JavaScript

ECMA and the TC39

ECMA is an international standards body
(much like ISO, IETF, W3C or WHATWG, for instance)

ES = ECMAScript. The official standard for JavaScript*

TC39 = Technical Committee 39. Caretaker for many standards:
ECMAScript (ECMA-262), Intl (ECMA-402), JSON (ECMA-404), etc.

* Which happens to be, these days, a U.S. trademark held by Oracle® Corporation. Yeah. I know.

The TC39 process

Meetings at various locations every 2 months. A yearly release, usually in June.

“ES6” ➡️ ES2015, “ES7” ➡️ ES2016,
and now we say ES2017, etc.

It’s all public.

The TC39 process

Language proposals go through 5 stages:

StageDescription
0 Strawman “It’d be dandy if we had a unicorn operator (🦄) to…”
1 Proposal A TC39 member “champions” the proposal. General API look and feel hammered down, and many/most cross-cutting concerns addressed.
2 Draft Initial spec text, covers all critical aspects and technical semantics.
3 Candidate Spec complete, verified by appropriate reviewers and greenlighted. API finalized, no stone left unturned.
4 Finished Full Test262 coverage, 2+ shipping implementations (e.g. V8 + SpiderMonkey), significant real-world usage feedback, Spec Editor imprimatur. Usually goes into the next feature freeze (January or March).

ES2017

(in case you missed it…)

Moar trailing commas!

spec


function yowza(
  key,
  increment,
) {
  // …
}
                

const yowza = (
  key,
  increment,
) => …
                

yowza(
  'itemScore',
  +10,
)
                

Remember: ES5 (2009…) already let you do these:


const roiDeLaClasse = {
  first: 'Georges',
  last: 'Abitbol',
}
                

const courses = [
  'ES Total',
  'Webpack',
]
                
Prettier : trailingCommas: 'all'

async / await

Async functions


async function createEntry(req, res) {
  try {
    const urlReq = await fetch(req.body.url)
    const html = await urlReq.text()
    const analysis = unfluff(html)
    const entry = await Entry.post({ … })
    // …
  } catch (err) {
    req.flash('error', `Une erreur est survenue en traitant cette URL : ${err.message}`)
    res.redirect('/entries/new')
  }
}
            

const [tags, entryCount, entries] = await Promise.all([
  Entry.tags(), Entry.count(), Entry.getEntries(req.query),
])
            

ES0218

(As per feature freezing in January 2018, may still expand)

Async iteration

Spec

Easily queue / loop over async requests.
Async iterables, iterators and generators use promises, so async/await work seamlessly with it.


for await (const line of readLines(filePath)) {
  console.log(line)
}
                

async function* readLines(path) {
  let file = await fileOpen(path);

  try {
    while (!file.EOF) {
      yield await file.readLine();
    }
  } finally {
    await file.close();
  }
}
                
Native: FF 57+, Ch63+, SF TP. No Edge or Node yet.
Babel: preset-env (shippedProposals) / plugin-transform-async-generator-functions

Rest / Spread Properties

Spec


// nonStandardAction = { type: 'progress', goalId: 1, increment: 2 }

const { type, ...payload } = nonStandardAction
// => payload = { goalId: 1, increment: 2 }
            

function doStuff(url, options = {}) {
  const opts = { ...DEFAULT_OPTIONS, ...options, regular: true }
  // …
}
            

return {
  ...state,
  history: tallyHistory(state),
  today: moment().format('YYYY-MM-DD'),
  todaysProgress: {},
}
            

Rest / Spread Properties

Spec

This is a new default behavior: you could already implement custom iterability for your objects.


class SuperDuper {
  [Symbol.iterator]() {
    // Prep and return an Iterator-compliant object:
    return {
      next() {
        // …
        return { done, value }
      }
    }
  }
}
            
Native: FF 57+, Ch63+, SF 11.1, Node 8. No Edge yet.
Babel: preset-env (shippedProposals) / plugin-transform-object-rest-spread

RegExp extravaganza!

RegExp named capture groups, RegExp Unicode property escapes,
RegExp lookbehind assertions, RegExp DotAll flag

XRegExp, your days are numbered… 😉


const RE_ISO_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u
console.log(RE_ISO_DATE.exec('2017-10-05').groups)
// => { year: '2017', month: '10', day: '05' }

'2017-10-05'.replace(RE_ISO_DATE, '$<day>/$<month>/$<year>')
// => '05/10/2017'

const RE_DIGITS = /^\p{Decimal_Number}+$/u
RE_DIGITS.test('𝟏𝟐𝟑𝟜𝟝𝟞𝟩𝟪𝟫𝟬𝟭𝟮𝟯𝟺𝟻𝟼') // => true

const RE_ANYTHING_GOES = /foo.bar/s
RE_ANYTHING_GOES.test('foo\nbar') // => true
            
Native: Ch64+, SF 11.1 (except lookbehind). No Node, FF or Edge yet.
Babel: preset-stage-3 + plugin-transform-modern-regexp

Other ES2018 tidbits

Promise#finally, Template literals revision

Always-invoked callback, much like try…finally… :


fetch('/api/v1/ohai')
  .then(…)
  .catch(…)
  .finally(…)
            

Illegal escape sequences in tagged template strings (TTS) don’t break, they only pass the raw string, so you can do this:


latex`\unicode`             // Despite \un not being a valid Unicode escape
windowsPath`C:\uuu\xxx\088` // Despite \uu, \xx and \08 not being valid escapes
            
Native: FF58+, Ch63+, SF 11.1. No Edge or Node yet.
Babel: preset-env (shippedProposals) for Promise#finally. Nothing for revised TTS.
Also, check out this book for great ES2018 explanations.

ES0219 & ES2020

chosen bits that’ll probably show up (stages 2–3)

(Next to no native support just now, FYI)

Class Fields

Spec


class APIConnector extends Component {
  // Public static field
  static propTypes = {
    apiKey: PropTypes.string.isRequired,
  }

  // Private (“for real”, a la C++ friend) instance field
  #secretToken = null

  // Public instance field (equiv. to this.awesome init inside constructor)
  awesome = true

  // …
}
            
Babel: stage 1 version available still (preset / class-properties), stage 2 WIP

Private methods and accessors

Spec

Just a follow-up on private fields…


class APIConnector {
  get #apiToken() {
    …
  }

  set #apiToken(value) {
    …
  }

  #checkConnectivity() {
    …
  }
}
            

import(…)

Dynamic import

Lazily import native ESM (dynamic specifier / code splitting).


import(`./l10n/${currentUser.locale}`)
  .then((locale) => app.l10n.use(locale))
  .catch((err) => app.reportError(err))
            

As this is Promise-based, you can get even fancier:


try {
  app.l10n.use(await import(`./l10n/${currentUser.locale}`))
} catch (err) {
  app.reportError(err)
}
            
Babel: preset-stage-3 / plugin-syntax-dynamic-import (and plugin-dynamic-import-node).
“Native” with Webpack 2+.

Optional catch binding

spec

Not using the exception? No need to bind it now!


try {
  return JSON.parse(await readFile(optionalConfigFilePath))
} catch {
  return {}
}
            
Native: FF58, Ch66, SF 11.1.
Babel: preset-env (with shippedProposals: true)

throw expressions

Spec

Not a statement anymore, but an expression. Spares us superfluous function wrapping.


function save(fileName = throw new TypeError('File name required')) {
  …
}

            

function getEncoder(encoding) {
  // Trigger warning: nested ternaries 😱
  const encoder = encoding === 'utf8' ? new UTF8Encoder()
                : encoding === 'utf16le' ? new UTF16Encoder(false)
                : encoding === 'utf16be' ? new UTF16Encoder(true)
                : throw new Error(`Unsupported encoding: ${encoding}`)
  …
}
            

Decorators

Spec


@mixin(SocialNetworkAddictMixin)
class Person {
  @deprecate
  facepalm() { … }

  @throttle('1hr', { leading: true })
  checkFacebook() { … }

  @memoize('15min')
  checkTwitter() { … }
}
                

class Person {
  @computedFrom('firstName', 'lastName')
  get fullName() {
    return `${this.firstName} ${this.lastName}`
  }
}
                

@connect(mapStateToProps, { logOut })
class SettingsScreen extends Component {
  @autobind
  openGoalAdder() { … }

  @override
  componentWillReceiveProps() { … }
}
                
Babel : stage-1 version in preset, plus legacy plugin. stage-2 WIP.

Numeric separator

Spec

Because most major languages have them! 😠


const FACTOR = 1_000_000_000
const FEE = 123_00

const FRACTION = 0.000_001

const BITS = 0b1010_0001_1000_0101
const FLAG = 0xDEAD_BEEF
            
Babel : preset-stage-1 (now stage 2, but…), plugin-transform-numeric-separator

Later

(maybe; stage 1)

Taking it easy with null and undefined

Optional Chaining and Nullish Coalescing


// Deep chaining despite missing slots? Hold my 🍺.
const userStreet = user?.address?.street

// Works for indirect indexing too
const userProp = user?.[propName]

// Optional method? There you go.
iterator.return?.()

// Default value IIF null/undefined?  Sure thing.
const chunkMode = settings.optimization?.splitChunks?.chunks ?? 'async'
            
Babel covers optional chaining: preset-stage-1 or transform-optional-chaining. WIP on nullish coalescing.

Véritables protocoles

First-class protocols


protocol RubyStyleEnumerable {
  // Symbol(s) to implement per-case
  _each

  // (Implemented) methods on top of these
  cycle (…) { … }
  count () { … }
  drop (…) { … }
  each (…) { … }
  map (…) { … }
  …
}
                

class Hash implements RubyStyleEnumerable {
  // Implement the symbol your way…
  [RubyStyleEnumerable._each] () { … }

  // …get the rest for free 😉
}
                

Another proposal, maximally-minimal mixins, covers some of the same needs. Unified proposal should emerge for stage 2.

do expressions

Spec


const x = do {
  const tmp = f()
  tmp * tmp + 1
}
                

Begone, nested ternaries!


const x = do {
  if (foo()) { f() }
  else if (bar()) { g() }
  else { h() }
}
                

Pretty cool for JSX too ➡️


return (
  <nav>
    <Home />
    {
      do {
        if (loggedIn) {
          <LogoutButton />
        } else {
          <LoginButton />
        }
      }
    }
  </nav>
)
                
Babel : preset-stage-1, plugin-transform-do-expressions

Pipeline operator

(FP FTW #1) Spec

Super-readable function composition. Inspired by similar features in OCaml, Elixir, Elm, F#…

Syntax is extremely in flux (4 competing proposals).
Here’s an example:


// Old-school
const result = exclaim(capitalize(doubleSay('hello')))

// Hip
const result = 'hello'
  |> doubleSay
  |> capitalize
  |> exclaim
            

Partial application

(FP FTW #2) Spec

Avoids hacky bind(…) usage. Can be done ad hoc, etc:


const mul = (x, y) => x * y
const mul3 = mul(?, 3)

const fx = (prefix, seed, ...items)
const oneSeededFx = fx(?, 1, ...)
            

Actually super advanced, lazy evals its binding context and arguments. Also combines deliciously with pipelines:


const newScore = player.score
  |> add(7, ?)
  |> clamp(0, 100, ?)
            

Function binding

(FP FTW #3) Spec • Warning: STAGE ZERO!

Use context-based free functions with ad-hoc binding. Sort of an alternative to mixins.


import { map, takeWhile, forEach } from 'iterlib'

getPlayers()
  ::map(x => x.character())
  ::takeWhile(x => x.strength > 100)
  ::forEach(x => console.log(x))
              
Babel : preset-stage-0, plugin-transform-function-bind

Pattern matching

(FP FTW #4) Spec • Warning: STAGE ZERO!

Heavily inspired by Rust, F# and Haskell. Code branching based on structural argument matching.
Early syntax looks like this:


const getLength = (vector) => match (vector) {
  { x, y, z }: Math.sqrt(x ** 2 + y ** 2 + z ** 2),
  { x, y }:    Math.sqrt(x ** 2 + y ** 2),
  [...]:       vector.length,
  else: {
      throw new Error("Unknown vector type");
  }
}
              

Thank you!

And may JS be with you.


Christophe Porteneuve

@porteneuve

Slides are up at bit.ly/confoo-es2020

https://joind.in/talk/e8ec5