Alors, quoi de neuf en ES2022 ?

Une présentation de Christophe Porteneuve à BlendWebMix 2019

whoami


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

ES2022 ?!

ECMA, TC39, ECMAScript et JavaScript

ECMA et le TC39

L’ECMA est un organisme international de standardisation
(comme l'ISO, l’IETF, le W3C ou le WHATWG, par exemple)

ES = ECMAScript. Le standard officiel pour JavaScript*

TC39 = Technical Committee 39. S’occupe de plusieurs standards :
ECMAScript (ECMA-262), Intl (ECMA-402), JSON (ECMA-404), etc.

* Qui, au passage, est une marque déposée (pour les U.S.) de Oracle Corp. Ouais, je sais 🤢

Le processus d’évolution du langage au TC39

Réunions tous les 2 mois, surtout aux U.S. Sortie annuelle en juin.

« ES6 » = ES2015, « ES7 » = ES2016, et on dit désormais ES2019, etc.

Tout ça est transparent et public.

Les 5 stades du processus au TC39

StadeDescription
0 Strawman « Hey ça serait cool d’avoir un opérateur licorne (🦄) pour… »
1 Proposal Un membre du TC39 devient « champion » d’une propale. L’aspect général de l’API est défini, et la plupart des cross-cutting concerns sont traités.
2 Draft Le texte initial de la spec est fait, et couvre tous les aspects critiques et la sémantique technique.
3 Candidate La spec est complète, vérifiée par qui de droit et approuvée. L’API est finalisée, aucune question ne reste en suspens.
4 Finished Couverture de tests Test262 intégrale, 2+ implémentations livrées (en général v8 et SpiderMonkey), retours d’expérience réelle significatifs, imprimateur du Spec Editor. Fera alors partie du prochain gel fonctionnel (entre janvier et mars), et donc la version annuelle concernée.

Rappels rapides : ES2018

ES2018 : Rest/Spread sur objets


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

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

            // Spread sur objets
            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 : la fête aux RegExp


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

            // Captures nommées
            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 (ici, positif)
            '$10.53'.match(/(?<=\$)\d+(?:\.\d+)?/) // => ['10:53', …]
          

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

ES2018 : Promise#finally

Est invoquée quel que soit l’état de la chaîne de promesses (accomplie ou rejetée), et n’altère pas cet état.
Donc tout à fait similaire au bloc finally d’un try.


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

ES2018 : itération asynchrone

Déclinaison basée promesses du protocole d’itération synchrone d’ES2015.
Au lieu d’implémenter Symbol.iterator, on implémente Symbol.asyncIterator.

Consommable facilement avec le nouveau for await (… of …)

Les flux Node (10+) et WHATWG (ex. Fetch) sont des itérables asynchrones.


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

Rappels rapides : ES2019

ES0219 : identifiant optionnel dans catch

Parfois on n’a pas besoin de l’erreur…


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

ES2019 : Object.fromEntries

La réciproque de Object.entries : construit un objet à partir de ses entrées.


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

ES2019 : String#trimStart/End

Complément le trim() d’ES2015 en permettant de choisir une seule extrémité de la chaîne.


            const spaceyText = '\n\t  bonjour monde !\u00a0\n\n'

            spaceyText.trimStart()
            // => 'bonjour monde !\u00a0!\n\n'

            spaceyText.trimEnd()
            // => '\n\t  bonjour monde !'
          

ES2019 : Array#flat/flatMap

flat(depth = 1) « aplatit » des tableaux imbriqués à la profondeur indiquée.


            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]) applique un map en aplatissant à la volée le résultat en profondeur 1.


              const data = ['Salut 😘 ça roule ? 😁', 'HBD 🎂 ma poule 🐔']
              const extractEmojis = (text) => text.match(/\p{Emoji}/gu)

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

ES2020 : ce qui est sûr…

ES2020 : String#matchAll

Récupère toutes les correspondances de groupe pour une regex sticky ou globale.


            const text = 'Appelez-moi au tel:0983450176 ou sms:478-555-1234'

            text.match(/(?[a-z]{3}):(?[\d-]+)/g)
            // => ['tel:0983450176', 'sms:478-555-1234'] -- 😞 OÙ SONT MES GROUPES ?!
          

            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(…)

Import de module ES (“ESM”) dynamique, basé promesse
(incidemment, cœur du code splitting automatique par Webpack)


            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

Nombres entiers en « précision aléatoire » (infinis) (> 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

3e combinateur de promesses (avec all et race). any arrive bientôt…

Le principe : on ne court-circuite pas au premier rejet / établissement, on récupère tous les établissements pour analyse.


            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

Enfin une standardisation de « l’objet global », quel que soit l’environnement !
(page web, frame, worker, processus, Node…)

Remplace, suivant le contexte : window, self, global et parfois this (sans parler des cas à la marge).

🔮 Et l’avenir alors ? 🔮

Séparateurs numériques 3

La lisibilité c’est le bien.


            const BILLION = 1_000_000_000 // On sait tout de suite…
            const FEE_CENTS = 20_00       // Cents / centiles
            const RATE = 2_90             // Idem
            const QUADS = 0b0010_1010     // “Quads” (4 bits)
            const TX_VALUE = 1_234_5418   // Virgule fixe (4), financier
            const WORDS = 0xDEAD_C0DE     // “Words” (2 octets)
          

Chaînage optionnel et Nullish coalescing 3

Pour faire des chaînes de propriétés résilientes mais pas rébarbatives.


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

            // APRÈS
            user?.names?.first
            user?.signup?.()
            user?.hobbies?.[0]
          

Pour mieux faire des valeurs par défaut.


              // 50 seulement si `options.duration == null`
              const duration = options.duration ?? 50
              const easing = options.advanced?.easing ?? 'inOutCubic'
            

await à la racine 3

Jusqu’ici, seulement dans une fonction async

Mais pratique pour de l’initialisation de module, etc.


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

            // Tant qu’à faire on parallélise les promesses…
            const engineModule = import(`./engines/${engine}.mjs`)
            const data = fetch(url)

            // Et hop !
            export const output = process((await engineModule).default, await data)
          

Déjà dans les REPL de Node, Chromium DevTools, Safari Web Inspector.

Nouveautés sur classes 3

Méthodes privées (dont accesseurs), champs d’instance et statiques (publics et privés)


            class APIClient extends Component {
              // Champ privé d’instance
              #oauthToken = null
              // Champ public static
              static propTypes = {
                authServer: URLPropType.isRequired,
              }
              // Champ public d’instance
              state = { authenticated: this.#oauthToken != null, loggedIn: false }

              // Méthode privée d’instance
              #shareAuthWith(recipient) {
                // Exige que `recipient` soit un `APIClient`
                recipient.#oauthToken = this.#oauthToken
              }
            }
          

Décorateurs 2

Pour l’AOP c’est tout de suite plus pratique… ES fournit la plomberie, l’écosystème les décorateurs opérationnels.


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

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

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

              @override
              render() { … }
            }
          

Temporal 2

Vise à remplacer (en mieux) Moment, Luxon, date-fns, etc.

Immutable, précis à la nanoseconde, toutes les TZ, distinction absolu/local, explicite…

Super complément à Intl et ses fonctions de formatage.

L’API est encore trop changeante, et la doc/spec trop aride, pour que je vous montre des exemples utiles ici.

Watch this space!™

Programmation fonctionnelle 1

Opérateur pipeline et application partielle 😍


                      // Particulièrement cool si on utilise des itérateurs
                      // (lazy donc efficace) !
                      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
              }
            

Protocoles 1

Encore mieux que les traits, les mixins ou les modules… Syntaxe temporairement, volontairement verbeuse.


                      protocol Foldable {
                        foldr // « Champ requis » (symbole)

                        // « Champ fourni » (ici, accesseur)
                        get length() { return this[Foldable.foldr]((m) => m + 1, 0) }
                        …
                        // Implémentation à la volée pour l’existant !
                        implemented by Array {
                          foldr(f, acc) { … }
                        }
                      }
                    

                      // Implémentation a posteriori
                      Set.prototype[Foldable.foldr] = (f, acc) => { … }
                      Protocol.implement(Set, Foldable)
                    

                    class NEList {
                      …
                      // Implémentation pour la classe en cours
                      implements protocol Foldable {
                        foldr(f, acc) { … }
                      }
                    }
                  

                    // Héritage (extension) de protocoles
                    protocol Mappable extends Foldable { map }
                    class Collection {
                      implements protocol Mappable {
                        foldr(f, acc) { … }
                        map(f) { … }
                      }
                    }
                  

Envie de cours vidéo ?

Delicious Insights fait des cours vidéo de dingue, notamment sur JS et Git.

Les auditeur·rice·s Blend Web Mix 2019 ont droit à un de nos cours payants… gratuitement !

Catalogue : bit.ly/di-cours-videoDemande le tien : bit.ly/blend-cours-video

(On fait aussi des pures formations de ouf, notamment ES Total)

Merci !

Always bet on JS.


Christophe Porteneuve

@porteneuve

Les slides sont sur bit.ly/blend-es2022