ECMAScript 6 – Harmony

In Harmonie vereint
Kommentare

Viele Entwickler, die ich kenne, fiebern neuen Softwarereleases entgegen. Es spielt dabei keine Rolle, ob es sich dabei um die neueste Version der Entwicklungsumgebung, eines Frameworks oder eines Sprachstandards handelt. Und so freuen wir uns nun auch auf die neueste Version von JavaScript. Aber warum jetzt genau? Was bietet diese neue, sechste Version des Standards, auf die Sie als Entwickler nicht länger verzichten können, und was davon sollten Sie unbedingt ausprobieren?

Um eines vorweg zu nehmen: Die Welt wird sich auch nach ECMAScript 6 weiterdrehen und die alltäglichen Problemstellungen, mit denen Sie bei der Entwicklung von JavaScript-Applikationen konfrontiert sind, werden nach wie vor bestehen. Der neue Sprachstandard macht Ihnen als Entwickler allerdings das Leben um ein kleines Stück einfacher und integriert eine Vielzahl von Features, in deren Genuss Sie bisher nur über die Einbindung von Bibliotheken gekommen sind. Aber eins nach dem anderen. Bevor Sie beginnen, Ihre Applikation auf Basis von ECMAScript 6 zu entwickeln, sollten Sie wissen, wie Sie an die neuen Features kommen und was die Kehrseite der Medaille ist.

Wie komme ich an die neuen Features?

Wie immer, wenn die perfekte Traumwelt auf die bittere Realität trifft, ist es auch bei ECMAScript 6 so, dass Sie nicht einfach loslegen und auf den gesamten Funktionsumfang des Standards zugreifen können. Sie als JavaScript-Entwickler sind es bereits seit Jahren gewohnt, mit unzureichender Unterstützung der Sprachfeatures in den verschiedenen Browsern zu kämpfen – und auch mit der neuen Version von JavaScript ändert sich an dieser Situation nichts. Die älteren Versionen der Browser unterstützen ECMAScript 6 entweder überhaupt nicht oder nur unzureichend. Aber auch in den neuesten Versionen der Browser ist der Standard noch nicht vollständig angekommen. An vorderster Front kämpfen die Entwickler von Firefox. Der Browser von Mozilla verfügt aktuell über eine der besten Unterstützungen, allerdings dicht gefolgt von Chrome. Weit abgeschlagen findet sich unser Freund, der Internet Explorer, mit seinen langen Releasezyklen. Auf GitHub hat sich kangax die Mühe gemacht und Informationen über die Unterstützung der Sprachfeatures gesammelt. Was aktuell wie ein rot-grüner Flickenteppich aussieht, ist das Ergebnis von Tests, die in JavaScript formuliert sind und mit den verschiedenen Browsern ausgeführt wurden (Abb. 1).

Rot-grüner Flickenteppich

Abb. 1: Rot-grüner Flickenteppich

Von den neuen Features sind im Internet Explorer in der Version 10 noch keine, und in Version 11 nur sehr wenige verfügbar. Anders sieht die Situation bei Firefox und Chrome in den jeweils neuesten Versionen aus. Aber auch hier ist man von einer umfassenden Unterstützung noch weit entfernt. Dann gibt es noch Browser wie beispielsweise Safari, bei dem sich jedoch ein ähnlich trauriges Bild wie beim Internet Explorer abzeichnet. Aber glücklicherweise gibt es zumindest für Experimente mit den neuen Features von JavaScript eine recht elegante Lösung: einen ECMAScript-6-ompiler, der auf den Namen Traceur hört und von Google als Open-Source-Projekt entwickelt wird. Binden Sie diesen Compiler in Ihre Applikation ein, kommen Sie auch auf älteren Browsern ohne Patches oder Ähnliches in den Genuss zahlreicher neuer Sprachfeatures. Diese Einbindung gestaltet sich relativ einfach. Alles was Sie hierfür benötigen, sind zwei Dateien aus dem Traceur-Projekt: Das ist zunächst die Datei traceur.js, die den Quellcode des eigentlichen Compilers enthält, und die bootstrap.js-Datei, die dafür sorgt, dass der Compiler gestartet wird. Haben Sie diese Voraussetzungen erfüllt, trennt Sie nur noch ein Schritt von der schönen neuen JavaScript-Welt. Sie müssen jedes Script-Tag, das ECMAScript-6-Quellcode enthält, mit dem Attribut type=“module“ versehen. Lassen Sie diesen letzten Schritt weg, erhalten Sie beispielsweise beim Einsatz von Klassen einen Syntaxerror. Der Nachteil des Compilers ist, dass auch er nicht sämtliche Sprachfeatures abdeckt. So können Sie, egal was Sie tun, das Structs-Feature (noch) nicht nutzen.

Auch serverseitig mit Node.js müssen Sie nicht auf die Annehmlichkeiten der Erweiterungen verzichten. Starten Sie Node.js mit der Kommandozeilenoption —harmony, wird die V8 Engine von Node.js in den ECMAScript-6-Modus geschaltet; und schon stehen einige der neuen Erweiterungen für die Entwicklung zur Verfügung.

Jetzt aber genug der Browserdiskussion. Im nächsten Schritt erfahren Sie mehr über die neuen Features, die Ihnen in Zukunft die Arbeit erleichtern sollen.

[ header = Seite 2: Das haben wir uns schon immer gewünscht ]

Das haben wir uns schon immer gewünscht

Es sind die kleinen Schritte und Änderungen, die einem Entwickler im täglichen Leben Freude bereiten. Wie oft waren Sie schon in einer Situation, in der Sie eine Aufgabe lösen mussten und sich eine bestimmte String- oder Array-Funktion gewünscht haben, die Sie aus einer anderen Sprache kennen und schätzen gelernt haben? In JavaScript haben Sie in diesem Fall zwei Optionen: Entweder Sie greifen auf eine Bibliothek wie underscore.js zurück, oder Sie erweitern selbst den jeweiligen Prototyp um die gewünschte Funktion. Wobei die zweite Variante keinesfalls empfehlenswert ist …

Die gute Nachricht ist, dass JavaScript in Zukunft um einige komfortable Funktionen im Bereich Strings und Arrays erweitert wird. Viele dieser Funktionen kennen Sie bereits aus dem Bereich Java oder PHP.

Der einfachste Fall ist die Wiederholung einer bestimmten Zeichenkette. Aktuell können Sie eine solche Aufgabe lediglich durch den Einsatz einer Schleife lösen. Mit der repeat-Methode auf dem String-Prototypen können Sie das dann einfach durch ‚a‘.repeat(15) ersetzen und erhalten 15 mal den Buchstaben „a“ als Ergebnis. Ein weiterer Block neuer String-Funktionen widmet sich der Lokalisierung bestimmter Zeichen. Die entsprechenden Funktionen heißen startsWith, contains und endsWith. Diese Funktionen sind nicht weltbewegend, aber zahlreiche Entwickler haben schon länger auf Features wie diese gewartet.

Ähnliches gilt für die neuen Array-Funktionen: Sie sind zwar nicht bahnbrechend, gestalten Ihnen das Leben jedoch erheblich angenehmer. Die wichtigsten Funktionen sind die find-Funktion und ihre kleine Schwester, die findIndex-Funktion. Diese beiden Funktionen ergänzen die aktuell existierende Palette von Funktionen, mit denen Sie anhand von Callback-Funktionen auf Arrays arbeiten können. Populäre Vertreter dieser Art von Hilfsfunktionen sind beispielsweise map und reduce. Mithilfe der find-Funktion können Sie ein bestimmtes Element in einem Array finden. Zu diesem Zweck formulieren Sie eine Callback-Funktion, innerhalb derer Sie prüfen, ob das aktuelle Element bestimmten Kriterien genügt. Ist das der Fall, verlassen Sie die Funktion einfach mit true als Rückgabewert, ansonsten mit false. Ist das erste Element gefunden, wird die Suche abgebrochen und der Vorgang ist beendet. Zur Formulierung der Bedingung haben Sie Zugriff auf das aktuelle Element, auf dessen Index und auf das ursprüngliche Array. Ein kurzes Beispiel, das diesen Sachverhalt verdeutlicht, finden Sie in Listing 1.

var arr = ['a', 'b', 'b', 'c'];

function findFunc(el, ind, ar) {
  if (el === 'b') {
    return true;
  }
  return false;
};

var el = arr.find(findFunc);

Strings und Arrays sind jedoch nicht die einzigen Objekte, die durch den neuen Standard eine Frischzellenkur erhalten. Auch der Object-Prototype, der Number-Prototype und das Math-Objekt erhalten zusätzliche Funktionen.

Die Erweiterungen der bestehenden Objekte erleichtern zwar in einigen Situationen die Arbeit mit JavaScript, sind jedoch nicht wirklich weltbewegend. Glücklicherweise sind das auch nicht die einzigen Stellen, an denen sich JavaScript weiterentwickelt. Mit ECMAScript 6 halten einige Konzepte und Technologien Einzug in den Sprachkern, die Sie als Entwickler wahrscheinlich seit Längerem produktiv einsetzen, allerdings stets unter Zuhilfenahme von verschiedenen Bibliotheken.

Zusätzliche Scopes und konstante Werte braucht das Land

Der Begriff Scope, also der Gültigkeitsbereich von Variablen, in JavaScript ist schnell erklärt. Grundsätzlich kennt die Sprache zwei Scopes, den globalen Scope und den Funktions-Scope. Es gehört zum guten Stil in der Entwicklung, vor jede Variablendefinition in JavaScript das Schlüsselwort var zu setzen. Innerhalb einer Funktion bewirkt es, dass die Variable nur innerhalb der Funktion und in darin enthaltenen Funktionen gültig ist. Verzichten Sie auf das var, erstellen Sie damit implizit eine Variable im globalen Kontext. Ein aktivierter Strict Mode schiebt dieser Vorgehensweise allerdings einen Riegel vor und quittiert das Ganze mit einem ReferenceError. Globale Variablen können Sie dennoch definieren, indem Sie explizit außerhalb einer Funktion mit var eine Variable definieren. Soviel zum aktuellen Stand der Entwicklung und jetzt zu den Neuerungen: JavaScript erhält einen zusätzlichen Scope.

Mit ECMAScript 6 wird JavaScript um Block-Scoping erweitert. Das bedeutet, dass Variablen so definiert werden können, dass sie nur in bestimmten Codeblöcken gültig sind. Zu diesem Zweck gibt es neben dem var-Schlüsselwort in Zukunft das let-Schlüsselwort. Die Verwendung von let gleicht der von var. Um nun eine Blockvariable zu definieren, setzen Sie bei der Definition der Variablen einfach statt dem bekannten var das let davor, und schon ist Ihre Variable nur in dem Block gültig, in dem sie definiert ist. Im Folgenden finden Sie ein Beispiel für den Einsatz von let:

for (let i = 0; i < 10; i++) {
  console.log(i);
}

Das führt zur nächsten Frage: Was ist ein Codeblock in JavaScript? Ein Codeblock kann beispielsweise eine for-Schleife oder ein Block innerhalb einer if-Bedingung sein. Mit let können Sie jetzt beispielsweise Ihre Zählervariablen in Schleifen nur für die aktuelle Schleife definieren. Ein wichtiger Unterschied zur Definition von Variablen mit var ist, dass die Definition von der Engine nicht an den Anfang der Funktion beziehungsweise des Blocks gesetzt wird. Greifen Sie vor der Definition auf die Variable zu, führt dies zu einem ReferenceError. Würde es sich hier nicht um Webentwicklung handeln, wäre alles zum Thema Block-Scopes gesagt. Allerdings haben Sie hier noch mit einigen Schwierigkeiten zu kämpfen: So unterstützt Chrome Block-Scopes nur mit aktiviertem Strict Mode, und Firefox möchte im Skripttag die Angabe der JavaScript-Version, was allerdings Chrome wiederum von der Interpretation des Skripts abhält. Hier zeigt sich wieder, ECMAScript 6 ist noch nicht so einsatzbereit, wie man es sich wünschen würde.

Aber wo Schatten ist, ist meist auch irgendwo ein Lichtblick zu finden. Und so ist es auch im Bereich der Variablen, beziehungsweise in diesem speziellen Fall eben nicht bei Variablen, sondern Konstanten. Der neue Standard definiert in JavaScript auch die Möglichkeit, konstante Werte zu definieren, die, einmal gesetzt, in Folge nur noch gelesen werden können. Diese Art der Definition wird bereits von Chrome und Firefox unterstützt, und auch der Internet Explorer bietet ab Version 11 Konstanten an. Zur Definition einer Konstanten verwenden Sie statt var oder let das Schlüsselwort const. Versuchen Sie eine Konstante zu verändern, passiert in den meisten Fällen im wahrsten Sinne des Wortes nichts – weder wird ein Fehler geworfen, noch eine Änderung des Werts der Konstanten vorgenommen, was auf den ersten Blick zwar nicht falsch, aber auch nicht wirklich erstrebenswert ist. Befinden Sie sich im Strict Mode, erhalten Sie, je nach verwendetem Browser, entweder einen SyntaxError oder einen TypeError, in jedem Fall wird aber der Ablauf Ihrer Applikation unterbrochen. Wie bereits angedeutet, gab es Konstanten bereits vor ECMAScript 6 – allerdings noch nicht standardisiert. Mit der jetzt erfolgten Standardisierung ändert sich allerdings auch das Scoping der Konstanten: Sie sind in Zukunft wie Variablen, die mit let definiert werden, an den aktuellen Block gebunden.

Neben dem Scoping und Konstanten gibt es allerdings noch zahlreiche weitere Änderungen im JavaScript-Standard. Eine davon rüttelt sogar an den Grundfesten der Sprache selbst.

[ header = Seite 3: Klassen, jetzt auch für JavaScript ]

Klassen, jetzt auch für JavaScript

Der herausragende Unterschied zwischen JavaScript und zahlreichen anderen Sprachen besteht darin, wie die Objektorientierung umgesetzt wird. Wo es in Java und PHP Klassen gibt, von denen Instanzen, also Objekte, erzeugt werden, haben Sie in JavaScript das Konzept der Prototypen. Prototypen stehen in diesem Fall nicht für Versuche oder Ähnliches, sondern für die Art, wie Sie Objekte vom gleichen Typ in JavaScript erzeugen können. In JavaScript gibt es Konstruktoren, also Funktionen, die in Verbindung mit dem new-Operator neue Objekte erzeugen. Jede Konstruktorfunktion verfügt über eine Eigenschaft mit dem Namen prototype. Alle Eigenschaften des prototype-Objekts, seien es nun Eigenschaften oder Methoden, werden in das neu generierte Objekt eingefügt. Eine Besonderheit des Prototypen ist, dass es sich dabei nicht um ein statisches Konstrukt wie bei einer Klasse handelt, die in der Regel im laufenden Betrieb nicht mehr angepasst werden kann – stattdessen können Sie den Prototypen zur Laufzeit Ihrer Applikation erweitern, aber auch Eigenschaften entfernen. Alle diese Modifikationen wirken sich auf alle Objekte aus, welche die Eigenschaften des Prototypen geerbt haben. Der prototypenbasierte Ansatz weist den Nachteil auf, dass Konstrukte wie Interfaces damit nicht umzusetzen sind. Auch eine Vererbung, wie Sie sie aus klassenbasierten Sprachen kennen, gibt es in JavaScript nicht. Um diesen Nachteil auszugleichen, haben Sie verschiedene Möglichkeiten: Entweder fügen Sie die gewünschten Funktionen direkt zum Prototypen hinzu, oder Sie erstellen Objekte über die Object.create-Methode, der als erstes Argument das Objekt übergeben wird, das als Prototyp dienen soll, welches wiederum einen eigenen Prototypen haben kann. Mit ECMAScript 6 ändert sich dieses Prinzip grundlegend, denn JavaScript erhält ein eigenes Klassensystem. Auf den ersten Blick bedeutet das, dass Sie in JavaScript jetzt über zwei Mechanismen verfügen, um Objekte von der gleichen Art zu erzeugen. Eine Variante besteht aus der Verwendung einer Konstruktorfunktion und deren Prototyp in Verbindung mit dem new-Operator. Die andere Variante ist die Definition einer Klasse mit dem class-Schlüsselwort. Aber keine Sorge, das bedeutet nicht die Abkehr von allen bisherigen Prinzipien von JavaScript, vielmehr wird durch die Klassen in JavaScript der Prototyp abstrahiert und bleibt im Hintergrund weiter erhalten. Mit den Klassen soll lediglich die Definition von Klassen kürzer und beschreibender werden. In Listing 2 sehen Sie, dass die Syntax im Vergleich zum bisherigen Prototypansatz wirklich aufgeräumter wirkt.

class Human {
  constructor() {
    this.legs = 2;
    this.position = {x: 0, y: 0};
  }	

  walk(x, y) {
    this.position.x += x;
    this.position.y += y;
  }
}

class Female extends Human {
  constructor() {
    super();
    this.gender = 'female';
  }
}

var lisa = new Female();

Der dynamische Charakter von JavaScript wird weiterhin erhalten bleiben. Ein kleiner Wehrmutstropfen ist jedoch, dass das Klassenfeature noch von keinem Browser unterstützt wird und Sie deshalb auf den Traceur-Compiler zurückgreifen müssen, wenn Sie dieses Feature nutzen möchten.

Sollten Sie sich jetzt fragen, was genau die neuen JavaScript-Klassen für Sie tun können, ist das eine durchaus berechtigte Frage. Die Syntax der Klassen ist zunächst etwas gewöhnungsbedürftig. Das Schlüsselwort constructor steht beispielsweise für die Konstruktorfunktion. Das function-Schlüsselwort sucht man in diesem Kontext allerdings vergeblich. Ähnliches gilt für Methoden. Was viele Entwickler freuen wird, ist die Tatsache, dass mit den Klassen auch Zugriffsmodifikatoren wie public und private kommen. Mit den Schlüsselwörtern get und set können Sie außerdem recht bequem Getter- und Setter-Methoden definieren. Diese werden beim lesenden beziehungsweise schreibenden Zugriff auf Eigenschaften einer Instanz der Klasse aufgerufen. In ECMAScript 5 wurde mit den Property-Deskriptoren bereits ein ähnliches Konstrukt vorgestellt.

Insgesamt erscheint das Klassenkonzept für JavaScript als eine solide Ergänzung zum teilweise sehr schwer verständlichen und wartungsunfreundlichen prototypenbasierten Ansatz.

Haben Sie sich schon mit Modulsystemen in JavaScript beschäftigt, wird es Sie freuen zu hören, dass auch dieses Konzept Einzug in den Standard halten wird.

Ein brandneues Modulsystem für Ihre Applikationen

Wächst Ihre JavaScript-Applikation, kommen Sie früher oder später in die Situation, dass Sie den Quellcode logisch unterteilen und diese Teile in verschiedene Dateien auslagern sollten. Der Nachteil dieser Methode liegt darin, dass Sie dann selbst dafür sorgen müssen, dass die einzelnen Dateien geladen werden, was bei einer großen Applikation schnell zu einer unübersichtlichen Kaskade von Skripttags führt. Noch schlimmer wird die Situation, wenn zwischen den einzelnen Komponenten oder Modulen Abhängigkeiten bestehen. Aber auch dieses Problem ist nicht gerade neu und so sind einige Bibliotheken entstanden, die sowohl das Laden von Dateien als auch die Auflösung von Abhängigkeiten übernehmen. Einer der bekanntesten Vertreter ist RequireJS. Den Kern dieser Bibliothek bilden die zwei Funktionen define und require, um Module zu definieren beziehungsweise Module als Abhängigkeit einzubinden. Ein Modul wird dabei in eine Funktion gekapselt. Der Rückgabewert eines solchen Moduls kann anderen Modulen wiederum als Abhängigkeit dienen. Der Ansatz, den RequireJS verfolgt, trägt den Namen AMD, Asynchronous Module Definition, der sich vor allem in der clientseitigen Entwicklung mit JavaScript sehr großer Beliebtheit erfreut. Eine Alternative dazu bietet das Modulsystem, das durch CommonJS spezifiziert ist. Die bekannteste Implementierung dieses Systems ist Node.js. Bei den CommonJS-Modulen exportieren Sie über ein quasi globales exports-Objekt das jeweilige Modul und können es dann an anderer Stelle mithilfe der require-Funktion einbinden.

ECMAScript 6 lässt beide bestehenden Standardisierungsversuche außer Acht und definiert einen dritten Standard, der das Ziel verfolgt, mit CommonJS-Modulen und AMD zusammenarbeiten zu können. Ein neues Modul definieren Sie mit dem Schlüsselwort module gefolgt von einer Zeichenkette, die den Namen des Moduls darstellt. Der eigentliche Quellcode des Moduls wird in geschweiften Klammern gekapselt. Mit dem Schlüsselwort export können Sie Objekte und Strukturen für andere Module verfügbar machen. Diese Exporte binden Sie dann unter Verwendung des import-Schlüsselworts ein. Haben Sie also ein Modul mit dem Namen Animals, das wiederum ein Objekt Dog exportiert, laden Sie es, indem Sie import * from Animals angeben. Danach können Sie direkt auf das Dog-Objekt zugreifen. Mit diesem Ansatz können Sie mit der Auflösung der Abhängigkeiten bereits einen Teil der Aufgaben abdecken. Was hier noch nicht gelöst ist, ist das dynamische Laden von Modulen. Zu diesem Zweck definiert der Standard einen so genannten Module Loader.

Der Module Loader sorgt automatisch dafür, dass Dateien geladen werden, die über import als Abhängigkeit angegeben werden. Um den Namen der Datei aufzulösen, die das Modul enthält, wird der aktuelle URL und der Name des Moduls gefolgt von der Dateiendung js verwendet. So wird aus dem Modul Human beispielsweise der URL http://localhost/js/Human.js, wenn sich die aktuelle Datei ebenfalls im /js/-Verzeichnis befindet. Den Loader können Sie auch manuell über die globale Funktion System.import ansprechen. Diese Funktion gibt eine Promise zurück, auf die Sie im Aufruf der then-Funktion zwei Callback-Funktionen registrieren können: Die erste Callback-Funktion wird aufgerufen, sobald das Modul erfolgreich geladen ist. Als Argument erhalten Sie in diesem Fall eine Referenz auf das Modul. Die zweite Callback-Funktion wird ausgeführt, falls beim Ladevorgang ein Fehler aufgetreten ist, sodass Sie entsprechend reagieren können.

Für das Modulsystem gilt leider Ähnliches wie schon für die Klassen: Aktuell werden weder das Modulsystem noch der Loader von den Browsern nativ unterstützt, weswegen Sie auf den Traceur-Compiler und ein weiteres Polyfill mit dem Namen es6-module-loader zurückgreifen müssen.

Ein weiteres Feature, das auf den ersten Blick oft unterschätzt wird, sind die neuen Iteratoren und Generatoren.

[ header = Seite 4: Schleifen, Iteratoren und asynchrone Funktionen mit Promises ]

Schleifen und noch viel mehr: Die neuen Iteratoren und Generatoren

ECMAScript 6 stellt ein Feature vor, das Sie aus anderen Sprachen bereits kennen: Iteratoren. Ein Iterator ist dabei ein Objekt, mit dessen Hilfe Sie über eine Datenstruktur iterieren können. Die Schnittstelle eines Iterators ist denkbar einfach: Sie verfügt über genau eine Methode, die next-Methode, mit der Sie das nächste Element auslesen können. Iteratoren sind standardmäßig sowohl für Objekte als auch für Arrays verfügbar. Ein solches Iterator-Objekt generieren Sie, indem Sie die Iterator-Funktion mit dem gewünschten Objekt als Argument aufrufen. Der Rückgabewert dieser Funktion ist das Iterator-Objekt, auf dem Sie dann die next-Methode aufrufen. Haben Sie alle Elemente des Objekts durchlaufen, wird bei einem weiteren Durchlauf eine Exception geworfen.

Wesentlich angenehmer als next manuell aufzurufen und mit eventuellen Exceptions umzugehen, ist es da schon, das Iterator-Objekt zusammen mit einer for-in-Schleife zu benutzen. Bei jedem Durchlauf erhalten Sie so Zugriff auf das aktuelle Element in Form eines Arrays, dessen erstes Element der Index und dessen zweites Element der Wert ist. Geben Sie beim Aufruf der Iterator-Funktion als zweites Argument den Wert true mit, erhalten Sie lediglich den Index. Der folgende Code verdeutlicht die Benutzung von Iteratoren:

var myObj = {name: 'Klaus', age: 42};
var it = Iterator(myObj);

console.log(it.next());

Seine wahre Stärke spielt ein Iterator aber nicht in der Standardform aus, sondern wenn Sie für bestimmte Konstruktoren eigene Iteratoren definieren. Zu diesem Zweck definieren Sie einen eigenen Konstruktor für den Iterator, der das gewünschte Objekt als Argument erhält. Auf dem Prototypen dieses Iterators definieren Sie dann die next-Methode, die entweder das nächste Element zurückgibt oder eine Exception wirft. Definieren Sie dann auf der prototype-Eigenschaft des ursprünglichen Konstruktors die __iterator__-Methode, die eine neue Instanz des Iterators zurückgibt, können Sie das Objekt mit dem benutzerdefinierten Iterator wie gewohnt in einer for-in-Schleife verwenden.

Eine elegante Alternative zu Iteratoren sind die Generatoren von ECMAScript 6. Einen Generator erstellen Sie, indem Sie in einer Funktion das Schlüsselwort yield verwenden. Die Funktion gibt dann keinen einfachen Wert, sondern ein Generatorobjekt zurück. Dieses Objekt implementiert ebenfalls die next-Methode. Diese Methode führt den Code der Generatorfunktion bis zum nächsten yield aus und gibt den entsprechenden Wert zurück. Das hat den Vorteil, dass Sie eine Generatorfunktion direkt der __iterator__-Eigenschaft eines Prototypen zuweisen können und diese dann, wie schon bei den Iteratoren, in einer for-in-Schleife verwenden können.

Damit enden jedoch die Möglichkeiten der Generatoren noch nicht. Sie können dieses Sprachfeature auch dazu nutzen, um eine Wrapper-Funktion um Promises zu generieren und so asynchrone Funktionen wie ihre synchronen Gegenstücke zu verwenden. Ein praktisches Beispiel für dieses Konzept ist das koa.js-Framework.

Wo wir gerade beim Thema Promises sind, auch dieses Werkzeug hält Einzug in den Sprachkern von JavaScript.

Bessere asynchrone Funktionen mit Promises

Promises sind, ganz einfach erklärt, das Versprechen, dass eine asynchrone Funktion irgendwann einmal abgeschlossen sein wird. Im Frontend hat beinahe jeder Entwickler schon mehr oder weniger bewusst mit Promises hantiert, der mit jQuery und der ajax-Funktion gearbeitet hat. Der Rückgabewert dieser Funktion bietet vor allem drei Methoden: done, fail und then. Diese Funktionen stehen Ihnen zur Verfügung, weil der Rückgabewert der ajax-Funktion ein Promise-Objekt ist. Laut Spezifikation können Sie unter anderem auf diese Methoden zugreifen, um auf den Erfolg oder Misserfolg einer asynchronen Operation zu reagieren. Die Reaktion erfolgt dabei über Callback-Funktionen, im Falle eines Erfolgs über die done-Methode, bei einem Fehlschlag über die fail-Methode oder mit then und einer Kombination aus beiden Fällen, indem Sie zwei Callback-Funktionen angeben – eine für den Erfolgsfall und eine für den Fehlerfall. Da sich Promises zunehmend im Rahmen verschiedener Bibliotheken zu einer Standardvorgehensweise etabliert haben, sind sie auch Bestandteil der ECMAScript-6-Spezifikation. Was in jQuery das Deferred-Objekt ist, erfüllt im Sprachkern nun der Promise-Konstruktor. Sie erzeugen ein Promise-Objekt, indem Sie den Konstruktor in Verbindung mit dem new-Operator benutzen. Der Konstruktor erhält eine Funktion als Argument, die wiederum die Funktionen resolve und reject als Argumente erhält. Innerhalb dieser Funktion formulieren Sie die asynchrone Funktionalität, die Sie abarbeiten möchten. Ist das Ende der Logik erreicht, rufen Sie entweder die resolve– oder die reject-Funktion auf. Optional können Sie diesen Funktionen Werte übergeben, die dann an die entsprechende Callback-Funktion weitergereicht werden. Auf dem Promise-Objekt können Sie nun, wie bereits erwähnt, über die done-, fail– und then-Methode auf die Erfüllung beziehungsweise den Fehlschlag der Promise mit Callback-Funktionen reagieren.

Zugegeben, das klingt etwas abstrakt. Deshalb in Listing 3 ein kleines Beispiel zum Thema Promises.

var promise = new Promise(function(resolve, reject) {
    setTimeout(function() {	
        resolve('Hallo Klaus');
    }, 1000);
});

promise.then(function(data) {
    console.log(data);
});

Wenn Sie sich jetzt fragen, welchen Sinn Promises ergeben, wenn Sie ja doch wieder auf Callback-Funktionen sitzen bleiben, ist diese Frage schnell beantwortet: Promises spielen Ihre Vorteile vor allem da aus, wo es darum geht, asynchrone Operationen zu verschachteln oder zu verketten. Das bedeutet, dass mehrere asynchrone Operationen erst nacheinander ausgeführt werden können, oder dass Sie auf die Beendigung mehrerer parallel ablaufender asynchroner Operationen warten möchten und die Ausführungsstränge danach wieder zusammenführen können.

Das war ein kurzer und bei Weitem nicht vollständiger Ausblick auf die Zukunft von ECMAScript 6 und die neuen Features, die Sie als Entwickler erwarten. Sie können also gespannt sein – in Zukunft wird einiges passieren im Bereich JavaScript.

Ausblick

Ganz harmonisch ist der neue JavaScript-Standard mit dem Codenamen Harmony noch nicht. Aber wie schon beim vorherigen Standard mit der Versionsnummer 5 müssen Sie den Browserherstellern noch einige Zeit einräumen, um alle Features umzusetzen. Außerdem dauert es erfahrungsgemäß eine Weile, bis sich auch die letzten Großunternehmen von ihren Browseraltlasten getrennt haben. Das heißt, dass sich die gewohnte Situation von Browserinkompatibilitäten erst mal nicht ändern wird. Allerdings setzen die Entwickler verschiedener Frameworks jetzt schon Zeichen, dass die Zukunft in ECMAScript 6 liegt. So baut beispielsweise die zweite Version von AngularJS stark auf die Features des neuen Standards. Auch das Node.js-Framework Koa.js setzt ebenfalls einige Features ein und kann aus diesem Grund nur mit dem —harmony-Flag von Node.js betrieben werden.

Wie auch schon in ECMAScript 5, gibt es auch in ECMAScript 6 Features, die ohne große Diskussion von vielen Entwicklern adaptiert werden. Aber auch solche, bei denen sich viele Personen fragen, ob die Welt dieses Feature wirklich gebraucht hat. Deswegen sollte man die Entwicklung mit JavaScript so handhaben, wie es Douglas Crockford empfiehlt: „I discovered that I could be a better programmer by using only the good parts and avoiding the bad parts.“

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -