Alors, quoi de neuf en ES2022 ?
Une présentation de Christophe Porteneuve à BlendWebMix 2019
Une présentation de Christophe Porteneuve à BlendWebMix 2019
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',
],
}
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.
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.
Stade | Description |
---|---|
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. |
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 }
// 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] // => 'Ζεύς'
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)
}
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}`)
}
}
catch
Parfois on n’a pas besoin de l’erreur…
function safeRequire(pathspec) {
try {
require(pathspec)
} catch {
debug(`Could not load optional dependency ${pathspec}`)
}
}
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))
)
}
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 !'
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) // => ['😘', '😁', '🎂', '🐔']
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' }]
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 })
}
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]
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… }
// ]
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).
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)
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 3Jusqu’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.
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
}
}
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
2Vise à 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!™
Opérateur pipeline et application partielle 😍
|
|
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
}
Encore mieux que les traits, les mixins ou les modules… Syntaxe temporairement, volontairement verbeuse.
|
|
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-video — Demande le tien : bit.ly/blend-cours-video
(On fait aussi des pures formations de ouf, notamment ES Total)
Christophe Porteneuve
Les slides sont sur bit.ly/blend-es2022