Was mit ECMAScript 2020 kommen wird

Nicht jede Funktion eines neuen ECMAScript-Standards ist für alle Projekte relevant. ECMAScript 2020 lässt jedoch eine hohe Alltagstauglichkeit erwarten.

Lesezeit: 6 Min.
In Pocket speichern
vorlesen Druckansicht Kommentare lesen 10 Beiträge

(Bild: Black Jack/Shutterstock.com)

Von

Im Sommer werden sieben Features im Rahmen von ECMAScript 2020 offiziell veröffentlicht. Außerdem gibt es eine Erweiterung der Spezifikation für die for-in-Schleife. Die neuen Funktionen sind unter anderem verkürzende Schreibweisen, ein neuer Datentyp und die Möglichkeit, die Performanz von Webseiten zu steigern. Dieser Artikel stellt die Neuerungen vor.

Young Professionals schreiben für Young Professionals

Dieser Beitrag ist Teil einer Artikelstrecke, zu der heise Developer explizit junge Entwickler*innen eingeladen hat – mit dem Ziel, ihresgleichen, aber natürlich auch die interessierten "älteren Semester" über aktuelle Trends, Entwicklungen, Phänomene und persönliche Erfahrungen zu informieren. Die Beiträge dieser Artikelstrecke erscheinen im monatlichen Turnus. Du bist selbst ein "Young Professional" und willst Teil dieser Serie sein? Dann bewirb dich mit einem Vorschlag bei der Redaktion. Sie steht dir mit hilfreichen Tipps über den Schreib-, Redigier- und Freigabeprozess hinweg zur Seite.

Der neue Optional Chaining Operator führt eine verkürzte Schreibweise ein, die eine einfachere Prüfung auf undefinierte Werte ermöglicht. Besonders bei der Arbeit mit Daten aus Schnittstellen lässt sich nie davon ausgehen, dass wirklich alle Daten enthalten sind. Es passiert häufig, dass ein Datensatz nur teilweise oder gar nicht befüllt ist. Damit die Software nicht abstürzt, etwa wenn man eine Funktion auf einem undefinierten Datensatz durchführt, ist zuerst zu prüfen, ob er ordnungsgemäß befüllt wurde. Die bisher gängige Schreibweise hierfür ist der ternäre Operator:

const tomaten = rezepte.pasta
? rezepte.pasta.tomaten
: undefined;

Wenn pasta definiert ist, speichere die Anzahl der Tomaten, ansonsten liefere undefined zurück. Diese Schreibweise kann schnell viel Platz einnehmen, zum Beispiel wenn gleich mehrere Ebenen des Objekts undefiniert sind. In dem Fall muss es für jede der Schichten einen eigenen ternären Operator geben.

Eine alternative Schreibweise ist die klassische Prüfung mit einer if-Konstruktion, in der die Funktionalität nur ausgeführt wird, wenn der Wert befüllt ist. Obwohl die Schreibweise genauso zum gewünschten Ergebnis führt, nimmt sie ebenfalls viel Platz ein. Das neue Optional Chaining bietet eine als ?. definierte, verkürzte Schreibweise:

const tomaten = rezepte.pasta?.tomaten;

Der Operator prüft, ob der Wert pasta befüllt ist und speichert dann die Anzahl der Tomaten. Die neue Erweiterung lässt sich beliebig oft hintereinander aufrufen, wenn mehrere Ebenen zu prüfen sind. Die Aufrufkette bricht ab, wenn eines der Elemente nicht befüllt ist, und liefert ein undefined zurück. Es ist auch möglich, den Operator auf ein Array anzuwenden.

const einkaufsliste = ['Eier', 'Milch'];
const pasta = einkaufsliste?.[7];

In dem Fall lässt sich durch den Optional Chaining Operator auf den Index eines Arrays zugreifen, ohne dass bekannt ist, ob das Array oder der spezifische Index vorhanden ist. Für Funktionen ist das Verhalten dasselbe.

rezepte.drucken?.('Suppe');

Wenn eine Funktion potenziell nicht definiert ist, lässt sich der Aufruf durch Optional Chaining trotzdem problemlos aufrufen. Ihren Parameter kann man bereits festlegen, ist sie dann nicht definiert, liefert der gesamte Ausdruck jedoch undefined zurück.

Um Abwärtskompatibilität zu gewährleisten, gibt es eine Konstellation, bei der ?. nicht zum Optional Chaining führt. Es gibt Fälle, in denen eine Zahl wie 0.03 als .03 abgekürzt wird, also die führende Null nicht ausgeschrieben ist:

const ausnahme = rezepte.pasta?.03:0;

Wenn die Zahl ohne führende Null in einem ternären Operator steht, lässt sie sich nicht mehr von einem Optional Chaining unterscheiden. Daher ist festgelegt, dass das neue Feature nicht ausgeführt wird, wenn auf ?. ein Zahlenwert folgt.

Besonders bei einer lang verketteten Prüfung auf undefinierte Werte schreibt sich das neue Feature deutlich lesbarer und kürzer als bisherige Alternativen. Der Optional Chaining Operator wird sich sicherlich schnell als Best Practice etablieren.

Der Nullish Coalescing Operator bietet eine zuverlässigere Möglichkeit, um im Fall eines undefinierten Werts eine Alternative abzuspeichern. Die bisher gängige Schreibweise hierfür ist die Oder-Verknüpfung.

const pasta = rezepte.pasta || {nudeln: '80g'};

Wenn also pasta undefiniert ist, wird das alternative Objekt gespeichert. Im Vergleich zu einem ternären Operator ist diese Schreibweise deutlich kürzer. Leider ist das Verhalten der Oder-Verknüpfung nicht immer wie erwartet:

const one = '' || 1;
const two = false || 2;
const three = 0 || 3;

In diesen Fällen wird der ursprüngliche Wert niemals gespeichert. Wenn der erste Wert einen Leerstring, den Zahlenwert Null oder den booleschen Wert false enthält, springt die Oder-Verknüpfung in die alternative Angabe. Der neue Nullish Coalescing Operator ist als ?? definiert. Ein unerwartetes Verhalten durch Sonderfälle gibt es nicht.

const anzahlTomaten = rezepte.pasta?.tomaten ?? '3';

Die Kombination aus dem Nullish Coalescing Operator und einen oder mehreren Optional Chainings kann lange Code-Fragmente einfach und lesbar halten. Während Google Chrome das Feature bereits unterstützt, ist es an vielen Stellen noch nicht einsetzbar. Die neue, praktische Schreibweise wird dennoch sicherlich bald im Arbeitsalltag Verwendung finden.

Ebenfalls für die kommende Version wurde Promise.allSettled angekündigt. Diese Erweiterung bereichert die Arbeit in asynchronem Kontext um eine vollständige Liste aller Anfragen – unabhängig davon, ob sie erfolgreich waren oder nicht. Damit grenzt es sich von Optionen wie den race- oder all-Funktionen ab.

Promise.race(ladeWoerterbuch);

Die race-Funktion speichert nur die schnellste Antwort und die erste, abgehandelte Anfrage, unabhängig davon, ob diese erfolgreich oder ein Fehlschlag war. Sobald die erste Antwort zurückgekommen ist, bricht die Aufrufkette ab und alle weiteren Anfragen werden nicht mehr beachtet. Handelt es sich beim ersten Versuch um einen Fehlversuch, lässt sich nicht prüfen, ob eine andere Anfrage erfolgreich gewesen ist.

Eine Liste mit mehreren, erfolgreichen Anfragen kann man durch die all-Funktion speichern. Sind alle Anfragen erfolgreich durchgeführt, wird ein Array mit allen Rückgabewerten erstellt.

await Promise.all(ladeWoerterbuch);

Die all-Funktion speichert alle erfolgreichen Anfragen oder zeigt eine Fehlerursache an. Ihr Nachteil ist, dass die Aufrufkette abgebrochen wird, sobald einer der Aufrufe fehlschlägt. In dem Fall wird nur der Ablehnungsgrund des Fehlschlags zurückgeliefert. Alle bisherigen, erfolgreichen Ergebnisse oder darauf folgenden Anfragen spielen keine Rolle mehr. Da die Aufrufkette direkt abbricht, ist es ebenfalls nicht möglich, mehrere, fehlgeschlagene Anfragen einzusehen. Die neue allSettled-Funktion ist in der Lage, eine vollständige Liste aller Antworten zu speichern.

const alleErgebnisse = await Promise.allSettled(
ladeWoerterbuch);
const erfolgreichGeladen = alleErgebnisse.filter(
p => p.status === 'fulfilled');

Die Aufrufkette wird bei der allSettled-Funktion nicht abgebrochen, unabhängig davon ob eventuell ein oder mehrere Fehler auftreten. Dann lässt sich die gespeicherte, vollständige Liste nach erfolgreichen oder fehlgeschlagenen Ergebnissen filtern. Die neue Erweiterung ermöglicht es, mit Teilergebnissen weiter zu arbeiten und im Fehlerfall alle Ursachen einsehbar zu machen.

Im neuen Standard ist außerdem der dynamische Import gelandet. Bisher steht nur der statische Import zur Verfügung, der für die meisten Anwendungsfälle auch gut funktioniert. Es ist jedoch nicht möglich zu erkennen, wie wichtig der jeweilige Import für die Ausführung des Programms ist.

Die neuen, dynamischen Imports ermöglichen ein optionales Nachladen. Das verbessert unter anderem die Performanz, denn bestimmte Teile lassen sich nachladen, wenn Benutzer*innen sich tatsächlich an der relevanten Stelle befinden. Wenn eine bestimmte Sprachauswahl ausgewählt ist, sind zum Beispiel die anderen Sprachen nicht mitzuladen. Durch den dynamischen Import ist es möglich, das schnellste, geladene Modul zu verwenden. Wenn eine Funktion von mehreren Anbieter*innen gleich ist, lässt sich einfach eine Liste an Optionen angeben. Zum Einsatz kommt dann das Modul, das am schnellsten geladen ist, und die restlichen Aufrufe werden abgebrochen. Ein denkbarer Anwendungsfall ist das Laden eines Wörterbuchs.

export const getImportExample = () => {
    const modul = './Woerterbuch.tsx';

    import(modul)
        .then((woerterbuch) => {
            woerterbuch.findeEintrag('Apfel');
            woerterbuch.getZufallseintrag();
        });
};

Wenn das Wörterbuch dynamisch geladen wurde, kann die Funktion normal genutzt werden. Für den Fehlerfall lässt sich ein catch-Block verwenden, um zum Beispiel eine alternative Funktion anzubieten oder ein anderes Modul zu laden. Der dynamische Import erlaubt es, den String, der den Ort des Moduls definiert, zur Laufzeit generieren zu lassen. Diese neue Funktion bietet viele Möglichkeiten, performantere Webseiten zu bauen oder das Datenvolumen für die mobile Versionen zu reduzieren.

Die kommenden Version des Standards erleichtert erneut die Arbeit mit regulären Ausdrücken. Um die Features aus den letzten Jahren besser nutzen zu können, hilft die matchAll-Funktion, zu den gefundenen Ergebnissen auch Metadaten auszugeben. Diese waren bisher nicht abrufbar. Die namensähnliche match-Funktion bietet die gleiche Suchfunktion, kann aber nur einen String zurückgegeben.

const text = 'This hex is: CAFE';
const regEx = /\b\p{ASCII_Hex_Digit}+\b/gu;
const match = text.match(regEx);
// "CAFE"

Die match-Funktion liefert die gefundene Hexadezimalzahl als String ohne Metadaten. Weitere Informationen wie der Index, an dem das Ergebnis gefunden wurde, oder im Ausdruck gesetzte Capturing Groups lassen sich nicht auslesen. Mit der Verwendung der neuen matchAll-Erweiterungen sind die Informationen direkt im Ergebnis enthalten.

const eintrag = Array.from(text.matchAll(regEx));
/* Eintrag: ["CAFE", index: 13, input:
"This hex is: CAFE", groups: undefined] */

Das matchAll-Feature liefert Treffer, Index, ursprünglichen Input und gegebenenfalls Capturing Groups. Das Ergebnis von matchAll gibt viele Metainformationen zum gefundenen Eintrag aus, ohne dass zusätzliche Programmierarbeit anfällt. Besonders vorteilhaft ist die Ausgabe der Capturing Groups, die seit 2018 für bessere Lesbarkeit in der Weiterverarbeitung der Teilergebnisse sorgt. Die Arbeit mit regulären Ausdrücken wird durch die matchAll also auch in diesem Jahr noch etwas angenehmer.

Mit der Einführung des neuen, primitiven Datentyps BigInt wird ein größerer Zahlenraum zugänglich gemacht. Sie ermöglicht, Ganzzahlen über 253 abzubilden, was bisher mit dem bestehenden Number-Typ die obere Grenze dargestellt hat.

const biggestInt = BigInt(
Number.MAX_SAFE_INTEGER + 1);

Mit dem neuen Konstruktor lässt sich der vergrößerte Zahlenraum nutzen. Wird das Feature vor dem offiziellen Release verwendet, sollte man vorab prüfen, ob der gewünschte Zielbrowser den Typen bereits unterstützt. Eine verkürzte Schreibweise zum Erstellen von BigInt-Werten wird bald mit der Kennzeichnung n möglich sein. Mit dem neuen Datentyp kann man wie gewohnt rechnen.

const bigIntAddition = biggestInt + BigInt(10);
const typeError = biggestInt - 10;

Wichtig ist zu beachten, dass man in Rechnungen Number- und BigInt-Werte nicht vermischen kann.

const expected = 5 / 2;
// Konsolenausgabe: 2.5
const unexpected = BigInt(5) / BigInt(2);
// Konsolenausgabe: 2n

Da es sich bei BigInt um Ganzzahlen handelt, werden die Nachkommastellen verworfen. Beim Ergebnis wird unabhängig von der Höhe der Nachkommastelle immer abgerundet. Sollten sehr präzise Berechnungen erfolgen, ist BigInt nicht die beste Wahl.

const bigIntAndString = BigInt(5) + '2';
// Konsolenausgabe: "52"

Das Addieren eines String- auf einen BigInt-Wert erzeugt einen neuen String. Der Zahlenwert von BigInt wird an den Text gehängt, gleich dem Verhalten eines Number-Werts. Generell handelt es sich um ähnliche, aber nicht gleiche Datentypen.

const willBeFalse = 5n === 5;
const willBeTrue = 5n == 5;

Wird ein BigInt- mit einem Number-Wert in einen strikten Vergleich (===) gesetzt, schlägt er immer fehl, da es sich um unterschiedliche Datentypen handelt. Ein abstrakter Vergleich (==) soll mit dem offiziellen Release noch ermöglicht werden.

Der primitive Datentyp BigInt ist sicherlich nicht in jedem Alltag nötig, aber gerade in den großen Zahlenräumen lässt sich zukünftig zuverlässig damit arbeiten.

Die Einführung von globalThis ermöglicht es schließlich allen Plattformen, ein einheitliches, globales Objekt in JavaScript zu verwenden. In Node.js kann man zum Beispiel nicht das window-Objekt nutzen. Dafür braucht es die Optionen global oder this. Andere Faktoren erlauben self, window und frames. Das Ergebnis ist dasselbe, aber die Zugriffsart unterscheidet sich. Einige Browser unterstützen die Erweiterung bereits jetzt.

Mit den neuen Funktionen in ECMAScript 2020 hat der Standard einige sehr alltagstaugliche Neuerungen erhalten. Der neue Nullish Coalescing Operator und das Optional Chaining ergänzen sich perfekt. Die alternative Schreibweise zum ternären Operator ist deutlich lesbarer und ohne unerwünschte Nebeneffekte. Gerade die Kombination aus beiden Features wird im Arbeitsalltag sicherlich häufig Verwendung finden.

Die Arbeit im asynchronen Bereich wird durch das allSettled-Feature bereichert. Durch die vollständige Ausgabe aller bearbeiteten Anfragen lassen sich im Fehlerfall alle Probleme erkennen, und es ist trotzdem noch möglich, mit erfolgreichen Teilanfragen weiter zu arbeiten. Mit der Einführung dynamischer Imports bieten sich viele, neue Optionen, die Performanz einer Webseite zu verbessern, indem bestimmte Module nur bei Bedarf nachgeladen werden.

Wie in den letzten Jahren wird der Umgang mit regulären Ausdrücken erleichtert. Das neue matchAll-Feature ermöglicht es, Metadaten, wie den Index und die festgelegten Capturing Groups, mit dem Treffer der Suche auszugeben.

Von allen Neuerungen hat der neue, primitive Datentyp BigInt die geringste Alltagstauglichkeit, da es nicht in allen Projekten einen Anwendungsfall für derart große Zahlenräume gibt. Für die Bereiche, die davon profitieren, wird er allerdings einen besonders großen Mehrwert erzeugen. Mit globalThis trägt der Standard schließlich etwas zur plattformübergreifenden Vereinheitlichung bei.

Es bleibt zu hoffen, dass sich der Standard im nächsten Jahr weiter in diese Richtung entwickelt.

Young Professionals schreiben für Young Professionals

Lisa Messerli
ist seit Anfang 2018 bei MATHEMA als Softwareentwicklerin tätig. Neben den Neuerungen der ECMAScript-Versionen interessieren sie Clean Code, Refactoring und Deep-Work-Themen.

(ane)