zurück zum Artikel

JavaScript: Die Neuerungen in ES2018

Sprachen
JavaScript: Die Neuerungen in ES2018

Der aktuelle Sprachstandard für JavaScript steht fest. Das ist eine gute Gelegenheit für einen Überblick über die Neuerungen.

In ihrem jüngsten Meeting [1] hat die TC39 die finale Version von ES2018 [2] verabschiedet. Damit erhält der JavaScript-Standard kurz aufgelistet folgende Neuerungen:

Asynchrone Iteration

Das in ES2015 eingeführte Konzept der nativen (synchronen) Iteration (Symbol.iterator) erhält in ES2018 das Gegenstück der nativen asynchronen Iteration [3] (Symbol.asyncIterator). Während bei synchronen Iteratoren der Aufruf von next() direkt das Ergebnis der jeweiligen Iteration zurückgibt, liefert der Aufruf bei asynchronen Iteratoren ein Promise-Objekt zurück. Dieses lässt sich wie gewohnt verarbeiten. Entwickler können asynchrone Generatoren definieren, indem sie vor function* ein async stellen:

const getRandomNumberAsync = () => 
new Promise((resolve, reject) => {
setTimeout(() => resolve(Math.random()), 5000);
});

// Asynchrone Generatorfunktion
async function* repeatRandomNumberAsync() {
while (true) {
const number = await getRandomNumberAsync();
yield number;
}
}

const asyncIterable = repeatRandomNumberAsync();
const asyncIterator = asyncIterable[Symbol.asyncIterator]();

Anschließend lässt sich der asynchrone Iterator wie gewohnt verwenden. Entweder über die Promise-API

asyncIterator.next()
.then(result1 => {
// { value: '0.8079568015936667', done: false }
console.log(result1);
return asyncIterator.next();
})
.then(result2 => {
// { value: '0.5766898916887389', done: false }
console.log(result2);
return asyncIterator.next();
})
.then(result3 => {
// { value: '0.06312379943064839', done: false }
console.log(result3);
return asyncIterator.next();
});

oder in Kombination mit async/await

const result1 = await asyncIterator.next();
console.log(result1);
const result2 = await asyncIterator.next();
console.log(result2);
const result3 = await asyncIterator.next();
console.log(result3);

oder direkt innerhalb einer for-of-Schleife:

(async () => {
for await (const number of repeatRandomNumberAsync()) {
console.log(number);
}
})();

Rest und Spread für Objekteigenschaften

Seit ES2015 könnnen sogenannte Rest-Parameter eine beliebige Anzahl an Funktionsparametern als Array bereitstellen. ES2018 führt analog dazu sogenannte Rest-Properties [4] ein, die etwas Ähnliches für Objekteigenschaften ermöglichen. Die Definition der Rest-Properties erfolgt ebenfalls über drei vorangestellte Punkte:

const { firstName, lastName, ...properties } = person;

Das sorgt im folgenden Listing dafür, dass alle Eigenschaften des Objekts person, die als enumerable definiert und nicht über die Destructuring-Regel einer anderen Variablen zugewiesen sind (im Beispiel firstName und lastName), in das Objekt properties kopiert werden:

const person = {
firstName: 'Max',
lastName: 'Mustermann',
age: 33,
hairColor: 'brown',
height: 1.8
};
const { firstName, lastName, ...properties } = person;
console.log(firstName); // Max
console.log(lastName); // Mustermann
console.log(properties); // { age: 33,
hairColor: 'brown',
height: 1.8 }

Spread-Properties [5] sind in gewisser Weise das Gegenstück zu Rest-Properties. Zur Erinnerung: Über den in ES2015 eingeführten Spread-Operator ist es möglich, in einem Array vorliegende Werte auf mehrere Elemente zu verteilen, beispielsweise auf Parameter. Spread-Properties funktionieren ähnlich für Objekte: die Anweisung

const person = { firstName, lastName, ...properties }; 

im folgenden Beispiel bewirkt das Kopieren aller Eigenschaften des Objekts properties (die enumerable sind) in das Objekt person:

const firstName = 'Max';
const lastName = 'Mustermann';
const properties = {
age: 33,
hairColor: 'brown',
height: 1.8
};

const person = {
firstName,
lastName,
...properties
};
// { firstName: 'Max',
// lastName: 'Mustermann',
// age: 33,
// hairColor: 'brown',
// height: 1.8 }
console.log(person);

Mit Spread-Properties lassen sich somit wie in folgendem Listing gezeigt Objekte klonen, ohne auf Object.assign() zurückgreifen zu müssen – wobei sich das im Detail etwas voneinander unterscheidet [6])).

const person = { firstName: 'Max', lastName:  'Mustermann', 
age: 33, hairColor: 'brown', height: 1.8 };
const clonedPerson = { ...person }; const
clonedPerson2 = Object.assign( {}, person);

Reguläre Ausdrücke

Bezüglich regulärer Ausdrücke wird es in ES2018 gleich mehrere Neuerungen geben. Über sogenannte Lookahead Assertions ist es bereits jetzt möglich, nach Mustern zu suchen, denen ein anderes Muster folgt oder eben genau nicht folgt.

Demnach unterscheidet man zwischen "Positive Lookaheads" und "Negative Lookaheads":

// Positive lookahead:
// Suche nach Mustern, denen das Muster "Mustermann" folgt:
const pattern = /\w+(?= Mustermann)/u;
const result = pattern.exec('Max Mustermann');
console.log(result[0]);
// result[0] === 'Max'

// Negative lookahead:
// Suche nach Mustern, denen das Muster
// "Mustermann" nicht folgt:
const pattern = /\w+(?! Mustermann)/u;
const result = pattern.exec('Max Defaultname');
console.log(result[0]);
// result[0] === 'Max'

Analog dazu wird es in ES2018 sogenannte Lookbehind Assertions [7] geben, mit denen sich nach Mustern suchen lässt, denen ein anderes Muster vorausgeht ("Positive Lookbehind Assertions") oder genau nicht vorausgeht ("Negative Lookbehind Assertions").

// Positive lookbehind:
// Suche nach Mustern, denen das Muster "Max " vorausgeht:
const pattern = /(?<=Max )\w+/u;
const result = pattern.exec('Max Mustermann');
console.log(result[0]);
// result[0] === 'Mustermann'

// Negative lookbehind:
// Suche nach Mustern, denen das Muster
// "Max " nicht vorausgeht:
const pattern = /(?<!Max )\w+/u;
const result = pattern.exec('Mustermann');
console.log(result[0]);
// result[0] === 'Mustermann'

Über sogenannte Named capturing groups [8] wiederum lassen sich innerhalb regulärer Ausdrücke Gruppen mit (eindeutigen) Namen versehen. Bislang sieht die Definition von Gruppen wie im folgenden Codeausschnitt aus, und die gefundenen Treffer lassen sich nach Anwendung des regulären Ausdrucks nur über den Index abrufen:

const pattern = /(\d{4})-(\d{2})-(\d{2})/u;
const result = pattern.exec('2018-01-31');
console.log(result[0]); // '2018-01-31'
console.log(result[1]); // '2018'
console.log(result[2]); // '01'
console.log(result[3]); // '31'

Benannte Gruppen erleichtern den Zugriff auf die Treffer und sorgen für verständlichere reguläre Ausdrücke. Die Definition verwendet einschließende spitze Klammern – im folgenden Ausschnitt <year>, <month> und <day>:

const pattern = 
/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
const result = pattern.exec('2018-01-31');

// Zugriff entweder über Namen:
console.log(result.groups.year); // '2018'
console.log(result.groups.month); // '01'
console.log(result.groups.day); // '31'

// Oder wie bisher auch über den Index:
console.log(result[0]); // '2018-01-31'
console.log(result[1]]; // '2018'
console.log(result[2]]; // '01'
console.log(result[3]]; // '31'

In Kombination mit Object Destructuring sind reguläre Ausdrücke ein wahrer Genuss, weil sich die benannten Gruppen darüber direkt gleichnamigen Variablen zuweisen lassen:

const pattern = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
const {
groups: {
year,
month,
day
}
} = pattern.exec('2018-01-31');
console.log(year); // '2018'
console.log(month); // '01'
console.log(day); // '31'

Darüber hinaus gibt es einige weitere Features bezüglich regulärer Ausdrücke. Über Unicode Property Escapes [9] ist es möglich, innerhalb von regulären Ausdrücken auf Eigenschaften von Unicode-Symbolen und deren Werte zuzugreifen. Das neu eingeführte s-Flag (dotAll-Flag [10]) sorgt zudem dafür, dass das Punktzeichen innerhalb von regulären Ausdrücken auch Zeilenenden als Treffer berücksichtigt:

// Ohne s-Flag:
console.log(/some.thing/.test('some\nthing')); // false

// Mit s-Flag
console.log(/some.thing/s.test('some\nthing')); // true

Erweiterung für finally

Neu ist zudem die Methode finally() [11], die für Promise-Objekte zur Verfügung stehen wird. Über das Schlüsselwort finally lässt sich bereits im aktuellen JavaScript-Standard bei einem try/catch-Statement ein Codeblock definieren, der in jedem Fall ausgeführt werden soll – unabhängig davon, ob ein Fehler auftritt oder nicht. Für synchrone Abläufe sieht das in JavaScript folgendermaßen aus:

const doSomething = () => {};
try {
doSomething();
console.log('Did something');
} catch(error) {
console.error('An error occured');
} finally {
// wird in jedem Fall aufgerufen
console.log('Executed in any case')
}

Seit ES2016 lässt sich das Vorgehen dank async/await auch bei asynchronen Abläufen verwenden:

const doSomethingAsync = () 
=> new Promise((resolve, reject) => {
setTimeout(resolve, 5000);
});

(async () => {
try {
await doSomethingAsync();
console.log('Did something');
} catch(error) {
console.error('An error occured');
} finally {
// wird in jedem Fall aufgerufen
console.log('Executed in any case')
}
})();

Die (parameterlose) finally()-Methode komplettiert das Ganze, indem sich darüber entsprechende Codeblöcke auch bei Verwendung der Promise-API definieren lassen:

const doSomethingAsync = () => new Promise((resolve, reject)
=> {
setTimeout(resolve, 5000);
});

doSomethingAsync()
.then((result) => console.log('Did something'))
.catch((error) => console.error('An error occured'))
// wird in jedem Fall aufgerufen
.finally(() => console.log('Executed in any case'));

Template Literal Revision

Die in ES2015 eingeführten Template Strings und die damit einhergehenden Tag Functions ermöglichen einen deutlich flexibleren Umgang mit Zeichenketten. ES2018 lockert einige Restriktionen, die für solche Zeichenketten noch existieren. Bisher gelten nach dem Backslash einige Zeichensequenzen nämlich als ungültig, beispielsweise wenn nach einem \u kein gültiger Unicode oder nach einem \x kein gültiger Hexcode steht. Völlige Freiheit bei der Definition von Template Strings hat man also bisher nicht – beispielsweise bei der Verarbeitung von LaTeX.

ES2018 hebt einige der syntaktischen Restriktionen im Rahmen der "Template Literal Revision" [12] auf. Die Eigenschaft raw innerhalb einer Tag Function enthält nun wirklich die unverarbeiteten Zeichenketten, ohne dass eine Überprüfung stattfindet. Ist in einer solche Zeichenkette allerdings ein syntaktischer Fehler enthalten, erhält die entsprechend verarbeitete Version den Wert undefined.

function latex(strings, ...values) {
// Verarbeitete Zeichenketten oder jeweils undefined:
console.log(strings);

// Unverarbeitete Zeichenkette:
console.log(strings.raw);
}

const latexDocument = latex`
// Funktioniert:
\newcommand{\fun}{\textbf{Beispiel}}
// Bisher Fehler, da ungültiges Token:
\newcommand{\unicode}{\textbf{Beispiel}}
// Bisher Fehler, da ungültiges Token:
\newcommand{\xerxes}{\textbf{Beispiel}}
`;

Fazit

ES2018 wird eine Reihe nützliche Neuerungen mit sich bringen. Wer sie vorab ausprobieren möchte, findet den Quelltext zu diesem Artikel im Git-Repository des Autors [13]. Eine Übersicht aller aktuellen Proposals (Stage 1 bis 3) findet sich ebenso im GitHub-Repository [14] von TC39 wie eine Übersicht der akzeptierten Proposals [15] (Stage 4). (rme [16])

Philip Ackermann
ist Autor mehrerer Fachbücher und Fachartikel über Java und JavaScript und arbeitet als Senior Software Developer bei der Cedalo AG in den Bereichen Industrie 4.0 und Internet of Things. Seine Schwerpunkte liegen in der Konzeption und Entwicklung von Node.js- und JEE-Projekten.


URL dieses Artikels:
https://www.heise.de/-3978938

Links in diesem Artikel:
[1] https://github.com/tc39/agendas/blob/master/2018/01.md
[2] https://tc39.github.io/ecma262/
[3] https://github.com/tc39/proposal-async-iteration
[4] https://github.com/tc39/proposal-object-rest-spread/blob/master/Rest.md
[5] https://github.com/tc39/proposal-object-rest-spread/blob/master/Spread.md
[6] http://2ality.com/2016/10/rest-spread-properties.html
[7] https://github.com/tc39/proposal-regexp-lookbehind
[8] https://github.com/tc39/proposal-regexp-named-groups
[9] https://github.com/tc39/proposal-regexp-unicode-property-escapes
[10] https://github.com/tc39/proposal-regexp-dotall-flag
[11] https://github.com/tc39/proposal-promise-finally
[12] https://tc39.github.io/proposal-template-literal-revision/
[13] https://github.com/cleancoderocker/es2018
[14] https://github.com/tc39/proposals
[15] https://github.com/tc39/proposals/blob/master/finished-proposals.md
[16] mailto:rme@ct.de