Evolution im Web: ECMAScript 2017

Der im Juni veröffentlichte aktuelle Sprachstandard für JavaScript bringt einige nützliche Neuerungen von async/await bis zu String Padding.

Sprachen  –  1 Kommentare
Evolution im Web: ECMAScript 2017

Ende Juni 2017 hat das Technical Committee 39 (TC39) die achte Ausgabe von ECMAScript veröffentlicht. Dabei handelt es sich um die standardisierte Fassung von JavaScript, die Ecma International herausgibt. Seit ECMAScript 2015 erscheint jedes Jahr eine neue Ausgabe des Sprachstandards. Bedingt durch diesen schnellen Entwicklungszyklus führte ECMAScript 2016 im vergangenen Jahr nur zwei kleinere Neuerungen ein.

Dieses Jahr haben die Macher mit ECMAScript 2017 wieder ein etwas größeres Paket geschnürt. Die neue Fassung enthält zwei größere und vier kleinere Features. Zu den größeren Neuerungen zählen die lang erwarteten asynchronen Funktionen mit den aus anderen Programmiersprachen bekannten Schlüsselwörtern async und await sowie Methoden zum Teilen von Speicherobjekten über Thread-Grenzen hinweg samt blockierender Synchronisations-Primitiven. Zu den kleineren Funktionen zählen die Ausgabe von Werten, Einträgen und Eigenschaftendeskriptoren eines Objekts, Methoden zum Auffüllen von Zeichenketten sowie die Möglichkeit, in Parameteraufzählungen bei Funktionen und Argumentlisten bei deren Aufruf abschließende Kommas zu verwenden. In guter Tradition gehören zusätzlich einige kleinere Änderungen an der Spezifikation dazu.

Der Artikel betrachtet die sechs größeren Features anhand einiger Beispiele. Die Kompatibilität der einzelnen Features mit (mobilen) Browsern, Serverplattformen wie Node.js und Source-to-Source-Compilern beziehungsweise Polyfills lässt sich mit der ECMAScript Compatibility Table nachvollziehen. Sie führt zudem die kleineren Änderungen der Spezifikation auf.

Das Highlight der neuen Sprachausgabe stellen sicherlich die asynchronen Funktionen mit den zugehörigen Schlüsselwörtern async und await dar, die aus Programmiersprachen wie Python oder C# bekannt sind. Das Schlüsselwortpaar erleichtert das Verwenden asynchroner Funktionen, da der resultierende Code eine synchrone Ablauffolge aufweist – im Gegensatz zu tief verschachtelten Callbacks ("Pyramid of Doom") oder den von Promises bekannten teils langen then-Aufrufketten. Das Sprachkonstrukt des Promise bildet jedoch die technische Basis für die neuen asynchronen Funktionen, da sie jeweils ein Promise zurückgeben müssen. Bei async und await handelt es sich um syntaktischen Zucker über das bereits in ECMAScript 2015 eingeführte Sprachkonstrukt.

async function showVersion() {
const version = await Promise.resolve('v1');
alert(`Current version is ${version}.`);
}

showVersion();

Das Beispiel zeigt die Auszeichnung der asynchronen Funktion mit dem Schlüsselwort async. Das System wartet an der Position des Schlüsselworts await, bis der Operand erfüllt (fulfilled) ist, das Promise also entweder (mit einem Wert) resolved wurde oder (mit einem Fehler) rejected. Entwickler dürfen das Schlüsselwort await nur in Funktionen verwenden, die sie mit dem Schlüsselwort async ausgezeichnet haben. Beim Aufruf der obigen Funktionen durch showVersion() löst sie zunächst das Promise auf und zeigt dann die Meldung "Current version is v1" an.

Vergleichbar einfach ist die Behandlung eines Fehlerfalls, also einer Promise-Rejection: Hierzu kommt analog zu synchronem Code der Try-Catch-Block zum Einsatz:

async function showVersion() {
try {
const version = await Promise.reject('Boo!');
alert(`Current version is ${version}.`);
} catch (err) {
alert(err);
}
}

showVersion();

Die Methoden Promise.resolve und Promise.reject dienen dazu, bereits feststehende Werte in einem Promise zu kapseln. Wesentlich spannender wird die Vorgehensweise beim Einsatz tatsächlich asynchroner Funktionen, etwa der Methode fetch, die dem Abruf von HTTP(S)-Ressourcen dient und ihrerseits ein Promise zurückgibt:

async function tellCharacter(id) {
const response =
await fetch(`https://swapi.co/api/people/${id}/`);
const person = await response.json();
alert(`Ich bin dein Vater, ${person.name}`);
}

tellCharacter(1);

Das Beispiel nutzt die Star-Wars-API, um einen Charakter aus dem Krieg-der-Sterne-Universum abzurufen. Neben dem Zugriff auf die Ressource ist auch das Deserialisieren der HTTP(S)-Antwort als asynchrone Operation modelliert. Die Funktion wartet auf das Abarbeiten beider Operationen jeweils mit dem Schlüsselwort await, ehe sie mit der Ausführung fortfährt. Die Star-Wars-API führt Luke Skywalker in der Charakterdatenbank unter der ID 1. Im Erfolgsfall lautet die Ausgabe nach dem Aufruf von tellCharacter(1) somit "Ich bin dein Vater, Luke Skywalker".

Das Beispiel zeigt die Verwandtschaft im Star-Wars-Universum.

Bei den meisten asynchronen Funktionsaufrufen sind die Schlüsselwörter nützlich. Durch die Verwendung von await ergibt sich ein praktischer Methodenfluss, dessen Ablauf leicht nachvollziehbar ist.

Die zum Zeitpunkt der Veröffentlichung aktuellen Ausgaben der wichtigsten Browser (Microsoft Edge 15, Mozilla Firefox 54, Google Chrome 60 und Apple Safari 10.1) unterstützen die asynchronen Funktionen ebenso wie die Serverplattform Node.js in Version 8 und teilweise die Source-to-Source-Compiler Babel und TypeScript. Letztere erlauben durch Transformationen und Polyfills die Verwendung bestimmter modernerer Sprachkonstrukte in früheren Fassungen von ECMAScript.