Quoi de neuf dans ES2020 ?

Une sélection de trucs cools de Christophe Porteneuve at Paris Web 2017

whoami


const christophe = {
  family: { wife: 'Élodie', son: 'Maxence' },
  city: 'Paris, FR',
  company: 'Delicious Insights',
  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, le TC39, ECMAScript et JavaScript

ECMA et le TC39

L’ECMA est un organisme international de standardisation.

(comme l’ISO, l’IETF ou le W3C, par exemple)

ES = ECMAScript. Le standard officiel de JavaScript*

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

* Qui est en fait aujourd’hui une marque d’Oracle aux US. Si. Je sais.

Le process d’évolution du langage

Réunions itinérantes tous les 2 mois. Une version officielle par an, en juin.

« ES6 » ➡️ ES2015, « ES7 » ➡️ ES2016, et désormais ES2017, etc.

Tout est public. Les propositions d’évolutions traversent 5 stades :

StadeDescription
0 Strawman « Ce serait bien si on avait un opérateur licorne (🦄) pour… »
1 Proposition Un membre du TC39 est « champion ». On a déjà débroussaillé la tête qu’aurait le code, l’impact sur l’existant, etc.
2 Brouillon Texte initial de la spec, couvre en détail tous les aspects critiques et la sémantique technique de la fonctionnalité.
3 Candidat La spec est complète, vérifiée par qui de droit et validée. L’API est finalisée, il ne reste aucune zone d’ombre.
4 Fini Couvert par Test262, implémenté en natif par 2+ moteurs répandus (ex. V8 et SpiderMonkey), REX significatif.

Rappels

ES2017

Virgules terminales

Trailing commas


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

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

yowza(
  'itemScore',
  +10,
)
                

N’oubliez pas que depuis ES5 (2009…) on pouvait déjà faire ça :


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

const courses = [
  'ES Total',
  'Webpack',
]
                

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

probablement (stade 3)

Itération asynchrone

Async iteration

Des files de requêtes faciles… Naturellement basé promesses et async/await.


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();
  }
}
                

Rest / Spread de propriétés

Rest / Spread properties


// 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: {},
}
            

Notez que c’est un nouveau comportement par défaut :
il était déjà possible de définir une itérabilité personnalisée pour nos objets.

Champs

Class fields


class APIConnector extends Component {
  // Champ statique (ici, public)
  static propTypes = {
    apiKey: PropTypes.string.isRequired,
  }

  // Champ d’instance privé (sans contournement possible) (sémantique "friend" C++)
  #secretToken = null

  // Champ d’instance public (valeur par défaut avant code du constructeur)
  awesome = true

  // …
}
            

import(…)

Dynamic import

Permet l’import de modules ES natifs dont le spécificateur n’est pas connu à l’avance
(ex. scenarii de code splitting pour les routes, langues, etc.)


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

Encore plus classe :


try {
  app.l10n.use(await import(`./l10n/${currentUser.locale}`))
} catch (err) {
  app.reportError(err)
}
            

La folie dans les RegExp !

RegExp named capture groups

RegExp Unicode property escapes

RegExp DotAll flag

XRegExp, tes jours sont comptés… 😉


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
            

ES0219

probablement (stade 2)

Méthodes et accesseurs privés

Private methods and accessors

La continuation logique des champs privés…


class APIConnector {
  get #apiToken () {
    …
  }

  set #apiToken (value) {
    …
  }

  #checkConnectivity () {
    …
  }
}
            

Séparateurs numériques

Numeric separators

Y’a pas de raison qu’on soit un des rares langages sans ! 😠


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
            

throw à la volée

Throw expressions

Plus seulement une instruction. Évite des fonctions d’enrobage superflues.


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

            

function getEncoder (encoding) {
  // Oui, c'est mochissime ces ternaires imbriqués, je sais…
  const encoder = encoding === 'utf8' ? new UTF8Encoder()
                : encoding === 'utf16le' ? new UTF16Encoder(false)
                : encoding === 'utf16be' ? new UTF16Encoder(true)
                : throw new Error(`Unsupported encoding: ${encoding}`)
  …
}
            

Décorateurs

Decorators


@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 () { … }
}
                

ES0219

ou plus tard (stade 1)

Chaînage optionnel

Optional Chaining


// Chaînage profond ? OKLM.
const userStreet = user?.address?.street

// Même pour l’indexation indirecte, hein…
const userProp = user?.[propName]

// Méthode optionnelle ? Aucun problème…
iterator.return?.()
            

Véritables protocoles

First-class protocols


protocol RubyStyleEnumerable {
  // Symbole(s) à implémenter au cas par cas
  _each

  // Méthodes (implémentées) par-dessus
  cycle (…) { … }
  count () { … }
  drop (…) { … }
  each (…) { … }
  map (…) { … }
  …
}
                

class Hash implements RubyStyleEnumerable {
  // On implémente le symbole à notre sauce…
  [RubyStyleEnumerable._each] () { … }

  // …et on a le reste gratuitement 😉
}
                
C’est tout frais ! Accepté en stade 1 jeudi dernier (28/09/2017) !

Merci !

Et que JS soit avec vous.


Christophe Porteneuve

@porteneuve

Les slides sont sur bit.ly/pw-es2020