Features von übermorgen: ES7 Async Functions

Tales from the Web side  –  2 Kommentare

Der Großteil der Web- und JavaScript-Entwickler wartet gespannt auf die endgültige Verabschiedung des ECMAScript6-Standards beziehungsweise den vollständigen Browser-Support, doch nur die wenigsten dürften sich mit Features beschäftigt haben, die für die übernächste Version ES7 auf dem Plan stehen. Gute Gelegenheit, sich ein Wesentliches davon einmal anzuschauen: die sogenannten Async Functions.

Übersicht darüber, inwieweit die einzelnen Features von ES7 bereits heute in den verschiedenen Laufzeitumgebungen implementiert sind, gibt die hier unterlegte Webseite. Man sieht: Die Unterstützung ist – logisch – bei Weitem noch nicht so fortgeschritten, wie es für ES6 der Fall ist (zum Vergleich), aber einige der Features lassen sich bereits in den Transpilern Google Traceur und Babel testen. Um die folgenden Codebeispiele auszuprobieren, ist es am einfachsten, die Online-Demo von Google Traceur zu verwenden. Die ES7-Features lassen sich dort unter dem Menü "Options" über den Menüpunkt "Show all options" aktivieren.

Asynchrone Programmierung kann in JavaScript schnell ganz schön haarig werden. Insbesondere, wenn asynchrone Funktionsaufrufe ineinander geschachtelt werden, leidet schnell die Lesbarkeit des Quelltextes. Als Ausweg aus dieser "Callback Hell" kann man als JavaScript-Entwickler auf Bibliotheken wie async oder mit ES6 auf Promises zurückgreifen. In ES7 dagegen will man mit den sogenannten Async Functions noch einen Schritt weiter gehen und das Kennzeichnen asynchroner Funktionen über zwei neue Schlüsselwörter noch stärker vereinfachen.

Über async wird dabei eine asynchrone Funktion als solche gekennzeichnet, wobei das Schlüsselwort vor die Deklaration der entsprechenden Funktion geschrieben wird. Das sorgt intern dafür, dass diese Funktion implizit immer ein Promise-Objekt zurückgibt, auch für den Fall, dass im Quelltext eigentlich ein anderer Wert zurückgegeben wird.

Der folgende Code …

// Options: --async-functions 
async function randomResult() {
var number = Math.floor(Math.random() * 4711);
if(number > 4711) {
return 'Success!';
} else {
throw 'Failure!';
}
}

… bewirkt das Gleiche wie folgender, auf Promises basierender Code:

function randomResult() {
var number = Math.floor(Math.random() * 4711);
if(number > 4711) {
return Promise.resolve('Success!');
} else {
return Promise.reject('Failure!');
}
}

Innerhalb einer mit async markierten asynchronen Funktion lässt sich nun das Schlüsselwort await verwenden. Stellt man dies einem Funktionsaufruf voran, wartet die aufrufende Funktion so lange, bis das Ergebnis des asychronen Funktionsaufrufs feststeht. Folgendes Listing zeigt dazu ein einfaches Beispiel. Die Funktion randomMessage() liefert zeitverzögert (um den Aspekt der Asynchronität zu simulieren) ausgehend von einer Zufallszahl entweder einen Fehler oder eben einen normalen Rückgabewert. Innerhalb der aufrufenden Funktion randomMessageTest() reicht nun die Angabe von await, um zu bewirken, dass die Ausführung der Funktion erst fortgesetzt wird, wenn die Ausführung von randomMessage() abgeschlossen ist.

// Options: --async-functions 
async function randomMessage() {
var number = Math.floor(Math.random() * 4711);
return new Promise(function(resolve, reject) {
setTimeout(function() {
if(number > 2222) {
resolve('Success: ' + number);
} else {
reject('Failure: ' + number);
}
}, 1000);
});
}
async function randomMessageTest() {
var message = await randomMessage();
console.log(message);
}
randomMessageTest();

Im Gegensatz zur Verwendung von Callback-Funktionen lässt sich dieser Quelltext viel einfacher lesen und verstehen. Besonders deutlich wird das, wenn wie in folgendem Listing mehrere asynchrone Funktionsaufrufe hintereinandergereiht werden. Was bei Callback-Funktionen zu einem unübersichtlichen Wust aus geschachtelten und eingerückten Funktionsaufrufen anwachsen würde, bleibt mit Async Functions deutlich übersichtlicher.

// Options: --async-functions 
async function random() {
var number = Math.floor(Math.random() * 4711);
return new Promise(function(resolve, reject) {
setTimeout(function() {
if(number > 2222) {
resolve('Success: ' + number);
} else {
reject('Failure: ' + number);
}
}, 1000);
});
}
async function randomMessageTest() {
var message = await randomMessageTest2();
console.log('randomMessageTest: ' + message);
return message;
}
async function randomMessageTest2() {
var message = await randomMessageTest3();
console.log('randomMessageTest2: ' + message);
return message;
}
async function randomMessageTest3() {
var message = await randomMessageTest4();
console.log('randomMessageTest3: ' + message);
return message;
}
async function randomMessageTest4() {
var message = await random();
console.log('randomMessageTest4: ' + message);
return message;
}
randomMessageTest();

Der Vorschlag der Async Functions befindet sich bei TC39 momentan in Stufe 1 ("Proposal") des vierstufigen Prozesses zur Aufnahme in den ECMAScript-Standard. Bislang unterstützt noch kein Browser das Feature, sodass man derzeit auf Transpiler wie Google Traceur angewiesen ist.