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
A handpicked selection of cool stuff by Christophe Porteneuve at Confoo Montréal 2018
https://joind.in/talk/e8ec5
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'
],
}
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.
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.
Language proposals go through 5 stages:
Stage | Description |
---|---|
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). |
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',
]
async
/ await
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),
])
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();
}
}
// 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: {},
}
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 }
}
}
}
}
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
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
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
// …
}
Just a follow-up on private fields…
class APIConnector {
get #apiToken() {
…
}
set #apiToken(value) {
…
}
#checkConnectivity() {
…
}
}
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)
}
Not using the exception? No need to bind it now!
try {
return JSON.parse(await readFile(optionalConfigFilePath))
} catch {
return {}
}
throw
expressionsNot 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}`)
…
}
@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() { … }
}
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
null
and undefined
// 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'
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
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>
)
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
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, ?)
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))
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");
}
}
Christophe Porteneuve
Slides are up at bit.ly/confoo-es2020