So, what’s new in ES2022?

A presentation by Christophe Porteneuve at Confoo Montréal 2020

whoami


const christophe = {
  family: { wife: 'Élodie', sons: ['Maxence', 'Elliott'] },
  city: 'Paris, FR',
  company: 'Delicious Insights',
  trainings: ['360° ES', 'Modern Web Apps', 'Node.js', '360° Git', 'Webpack'],
  webDevSince: 1995,
  mightBeKnownFor: [
    'Prototype.js',
    'Prototype and Script.aculo.us',
    'dotJS',
    'Paris Web',
    'NodeSchool Paris',
  ],
}
          

ES2022 ?!

ECMA, TC39, ECMAScript and JavaScript

ECMA and TC39

ECMA is an international standards body
(like ISO, IETF, W3C or WHATWG, to name a few)

ES = ECMAScript. The official standard for JavaScript*

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

* Which happens to be, in the U.S., a trademark of Oracle Corp. Yeah, I know 🤢

The way the language evolves at TC39

Meetings every two months, mostly in the U.S. Yearly release in June.

“ES6” = ES2015, “ES7” = ES2016, and now we say ES2020, etc.

This is all transparent and public.

The 5 stages of the TC39 process

StageDescription
0 Strawman “Say, it’d be nifty to get a Unicorn (🦄) operator to…”
1 Proposal A TC39 member becomes the proposal’s “champion.” The general shape of the API is defined, and most of the cross-cutting concerns are handled.
2 Draft The initial spec text is done, and covers all critical aspects and the tech semantics.
3 Candidate The spec is complete, duly reviewed and approved. The API is finalized and no stone is left unturned.
4 Finished Full Test262 coverage, 2+ shipped implementations (usually v8 and Spidermonkey), significant real-world feedback, and imprimatur by the Spec Editor. Will then be part of the next feature freeze (January-March), hence ship in the associated yearly release.

Quick recap: ES2018

ES2018: Rest/Spread properties


            class OldSchoolHOC extends Component {
              render() {
                // Rest on properties
                const { component: Component, ...props } = this.props

                return (
                  // …
                  <Component {...props} />
                )
              }
            }
          

            // Spread on properties
            const DEFAULTS = { first: 'John', last: 'Doe' }
            const person = { last: 'Smith', age: 42 }
            const result = { ...DEFAULTS, ...person, age: 36 }
            // => { first: 'John', last: 'Smith', age: 36 }
          

ES2018: RegExp galore


            // Flag: “dotAll” / “singleLine”
            '<p>line 1\nline 2\nline 3</p>'.match(/<p>.*<\/p>/s) // => '<p>…</p>'
          

            // Named captured groups
            const ATTR = /\s+(?<name>\w+)=(?<quote>['"])(?<value>.+?)\k<quote>/
            const { name, quote, value } = '<p class="foo"></p>'.match(ATTR).groups
            // => name === 'class', quote === '"', value === 'foo'

            '<p class="foo"></p>'.replace(ATTR, '[$<name>=$<value>]')
            // => '<p[class=foo]></p>'
          

            // Lookbehinds (here a positive one)
            '$10.53'.match(/(?<=\$)\d+(?:\.\d+)?/) // => ['10:53', …]
          

            // Unicode Property Escapes
            'By Ζεύς!'.match(/\p{Script=Greek}+/u)[0] // => 'Ζεύς'
          

ES2018: Promise#finally

Invoked regardless of the promise chain’s outcome (fulfilled or rejected), and does not alter that state.
So very much like the finally block for a try.


            function doSomeOldSchoolAsync() {
              return Promise.resolve()
                .then(setUp)
                .then(onSuccess, onError)
                .finally(anyway)
            }
          

ES2018: asynchronous iteration

A promise-based variation of the synchronous iteration from ES2015.
Instead of implementing Symbol.iterator, we implement Symbol.asyncIterator.

Easily consumable using the new for await (… of …)

Node (10+) and WHATWG streams (e.g. Fetch) are asynchronous iterables.


            async function echoLines(filename) {
              const input = createReadStream(filename, 'utf-8')
              for await (const line of input) {
                console.log(`> ${line}`)
              }
            }
          

Quick recap: ES2019

ES0219: optional catch binding

Sometimes you don’t need the error…


            function safeRequire(pathspec) {
              try {
                require(pathspec)
              } catch {
                debug(`Could not load optional dependency ${pathspec}`)
              }
            }
          

ES2019: Object.fromEntries

The inverse of Object.entries: builds an object from its key/value pairs.


            function pick(src, ...propertyNames) {
              return Object.fromEntries(
                Object.entries(src).filter(([name]) => propertyNames.includes(name))
              )
            }
          

ES2019: String#trimStart/End

Finer-grained variations of ES2015’s trim() that let us pick an extremity of the string.


            const spaceyText = '\n\t  hello world!\u00a0\n\n'

            spaceyText.trimStart()
            // => 'hello world!\u00a0!\n\n'

            spaceyText.trimEnd()
            // => '\n\t  hello world!'
          

ES2019: Array#flat/flatMap

flat(depth = 1) “flattens” nested arrays up to a given depth.


            const data = ['Alice', ['Bob', 'Claire'], ['David', ['Erin', 'Fred'], 'Georges']]

            data.flat()  // => ['Alice', 'Bob', 'Claire', 'David', ['Erin', 'Fred'], 'Georges']
            data.flat(2) // => ['Alice', 'Bob', 'Claire', 'David', 'Erin', 'Fred', 'Georges']
          

flatMap(mapper[, thisArg]) runs a map whilst flattening the result on the fly, at depth 1.


              const data = ['Hey 😘 wassup? 😁', 'HBD 🎂 sweetheart 💖']
              const extractEmojis = (text) => text.match(/\p{Emoji}/gu)

              data.map(extractEmojis)     // => [['😘', '😁'], ['🎂', '💖']]
              data.flatMap(extractEmojis) // => ['😘', '😁', '🎂', '💖']
            

ES2020: what’s guaranteed…

ES2020: String#matchAll

Grabs all group matches for a sticky or global regex.


            const text = 'Get in touch at tel:0983450176 or sms:478-555-1234'

            text.match(/(?<protocol>[a-z]{3}):(?<number>[\d-]+)/g)
            // => ['tel:0983450176', 'sms:478-555-1234'] -- 😞 WHERE THEM GROUPS AT?!
          

            Array.from(text.matchAll(/([a-z]{3}):([\d-]+)/g)).map(
              ([, protocol, number]) => ({ protocol, number })
            )
            // => [{ number: '0983450176', protocol: 'tel' }, { number: '478-555-1234', protocol: 'sms' }]

            Array.from(text.matchAll(/(?<protocol>[a-z]{3}):(?<number>[\d-]+)/g)).map((mr) => mr.groups)
            // => [{ number: '0983450176', protocol: 'tel' }, { number: '478-555-1234', protocol: 'sms' }]
          

ES2020: import(…)

Dynamically imports ES Modules (“ESM”), using promises
(incidentally, this is the heart of Webpack’s automatic code splitting)


            async function evaluate(code) {
              const Babel = await import('@babel/standalone')

              return Babel.transform(code, {
                ast: true,
                filenameRelative: 'exercice.js',
                presets: ['stage-3'],
              })
            }

            async function loadLocale(locale) {
              const locale = await import(`./locales/${locale}`)
              LocaleManager.registerLocale(locale, { activate: true })
            }
          

ES2020: BigInt

“Arbitrary precision” (infinite) integers (> 253)


            const theBiggestInt = 9007199254740991n

            const alsoHuge = BigInt(9007199254740991)
            // => 9007199254740991n

            const hugeButString = BigInt('9007199254740991')
            // => 9007199254740991n

            const bigN = 2n ** 54n
            // => 18014398509481984n

            [42n === 42, 42n == 42]
            // => [false, true]
          

ES2020: Promise.allSettled

3rd promise combinator (with all and race). any is at stage 3.

The idea: doesn’t short-circuit, be it on the first rejection or fulfillment: we get all settlements for analysis.


            await Promise.allSettled([wait(100), wait(50), throwAfter(75)])
            // => [
            //   { status: 'fulfilled', value: 100 },
            //   { status: 'fulfilled', value: 50 },
            //   { status: 'rejected', reason: Error: 75 at… }
            // ]
          

ES2020: globalThis

Finally a standardization of the “global object,” regardless of the environment!
(web page, frame, worker, process, Node…)

Depending on context, aliases to: window, self, global and sometimes this (not to mention edge cases).

ES2020: Optional chaining and Nullish coalescing 3

To make resilient yet readable property chains.


            // BEFORE
            user != null && user.names != null && user.names.first
            user != null && user.signUp != null && user.signUp()
            user != null && user.hobbies != null && user.hobbies[0]

            // AFTER
            user?.names?.first
            user?.signup?.()
            user?.hobbies?.[0]
          

To better handle default values.


              // 50 only if `options.duration == null`
              const duration = options.duration ?? 50
              const easing = options.advanced?.easing ?? 'inOutCubic'
            

🔮 So what’s coming next? 🔮

Numeric separators 3

It’s nicer when you can read it.


            const BILLION = 1_000_000_000 // You immediately know
            const FEE_CENTS = 20_00       // Cents / centiles
            const RATE = 2_90             // Ditto
            const QUADS = 0b0010_1010     // “Quads” (4 bits)
            const TX_VALUE = 1_234_5418   // Fixed point (4), financial
            const WORDS = 0xDEAD_C0DE     // “Words” (2 bytes)
          

Top-level await 3

Until now, we could only await in an async function…

But this can be super-handy for module initialization, etc.


            import { engine, process } from './some-module.mjs'

            // Might as well run these in parallel…
            const engineModule = import(`./engines/${engine}.mjs`)
            const data = fetch(url)

            // And presto!
            export const output = process((await engineModule).default, await data)
          

Already in Node REPL, Chromium DevTools console, Safari Web Inspector console.

Class novelties 3

Private methods (incl. accessors), static and instance fields (public / private)


            class APIClient extends Component {
              // Private instance field
              #oauthToken = null
              // Public static field
              static propTypes = {
                authServer: URLPropType.isRequired,
              }
              // Public instance field
              state = { authenticated: this.#oauthToken != null, loggedIn: false }
              // Private instance method
              #shareAuthWith(recipient) {
                // Demands that `recipient` be an `APIClient`
                recipient.#oauthToken = this.#oauthToken
              }
            }
          

Decorators 2

So much nicer for AOP… ES provides the plumbing and the ecosystem provides operational decorators.


            class SuperWidget extends Component {
              @deprecate
              deauth() { … }

              @memoize('1m')
              userFullName() { … }

              @autobind
              logOut() {
                this.#oauthToken = null
              }

              @override
              render() { … }
            }
          

Temporal 2

Aims to (beneficially) replace Moment, Luxon, data-fns, etc.

Immutable, nanosecond-precise, has all TZ, distinguishes absolute vs. local, explicit…

Great complement to Intl and its formatting functions.


            const meeting1 = Temporal.Date.from('2020-01-01')
            const meeting2 = Temporal.Date.from('2020-04-01')
            const time = Temporal.Time.from('10:00:00')
            const timeZone = new Temporal.TimeZone('America/Montreal')

            const absolute1 = timeZone.getAbsoluteFor(meeting1.withTime(time))
            // => 2020-01-01T15:00:00.000Z
            const absolute2 = timeZone.getAbsoluteFor(meeting2.withTime(time))
            // => 2020-01-01T14:00:00.000Z
          

Check out Maggie’s awesome talk at dotJS 2019!

Moar functional programming 1

Pipeline operator + partial application = 😍


                    // Particularly cool when we use iterators
                    // (lazy hence efficient)!
                    const result = numbers
                      |> filter(?, (v) => v % 2 === 0)
                      |> map(?, (v) => v + 1)
                      |> slice(?, 0, 3)
                      |> Array.from
                    

                    function* filter(items, predicate) {
                      for (const item of items) {
                        if (predicate(item)) {
                          yield item
                        }
                      }
                    }
                  

Pattern matching! 😍


              const getLength = (vector) => case (vector) {
                when { x, y, z } -> Math.sqrt(x ** 2 + y ** 2 + z ** 2)
                when { x, y } -> Math.sqrt(x ** 2 + y ** 2)
                when [...etc] -> vector.length
              }
            

Protocols 1

This is even better than traits, mixins or modules… The syntax is temporarily—and intentionally—verbose.


                    protocol Foldable {
                      foldr // “Required field” (symbol)

                      // “Provided field” (here an accessor)
                      get length() { return this[Foldable.foldr]((m) => m + 1, 0) }
                      …
                      // On-the-fly implementation for existing host types!
                      implemented by Array {
                        foldr(f, acc) { … }
                      }
                    }
                  

                    // Later implementation
                    Set.prototype[Foldable.foldr] = (f, acc) => { … }
                    Protocol.implement(Set, Foldable)
                  

                    class NEList {
                      …
                      // Implementation for the current class
                      implements protocol Foldable {
                        foldr(f, acc) { … }
                      }
                    }
                  

                    // Inheriting (extending) protocols
                    protocol Mappable extends Foldable { map }
                    class Collection {
                      implements protocol Mappable {
                        foldr(f, acc) { … }
                        map(f) { … }
                      }
                    }
                  

Do you like screencasts?

We publish about one new video course every month, mostly around Git and JS.


screencasts.delicious-insights.com


Or if that’s easier for you:


bit.ly/screencasts-confoo

Thank you!

Always bet on JS.


Christophe Porteneuve

@porteneuve

Slides are at bit.ly/confoo-es2022