Asyncrones Javascript

Permalink

In letzter Zeit habe ich in meinem kleinen Projekt – dem HTML5 Podcatcher – die Funktionen zum Speichern umgestellt. Bislang waren alle Daten mittels Web-Storage abgelegt. Die neue Fassung hingegen benutzt die Indexed Database API die ich auch schon für das Speichern von Dateien verwende.

Der größte Unterschied zwischen beiden Techniken liegt im grundsätzlichen Herangehen. Die Web-Storage API arbeitet synchron. Das heißt, ein Javascriptprogramm macht erst dann mit dem nächsten Befehlt weiter, wenn das laden oder speichern von Daten abgeschlossen ist. Die Indexed Database API hingegen arbeitet asynchron. Hier werden die Operationen zum Speichern und Laden im Hintergrund ausgeführt während das restliche Programm weiterläuft.

Dieses Verhalten hat einige Umstellungen zur folge. Da man nicht genau weiß wann ein Speichervorgang zu ende ist muss man irgendwie darauf reagieren. Hierzu kann man ein Event auslösen oder eine Callback-Funktion aufrufen. Eine neuere Technik sind Promises.

Callback Functions

Callback-Funktionen sind eine Möglichkeit das Ergebnis einer asynchronen Funktion zu verarbeiten. Beim Aufruf der Funktion wird dazu ein zusätzlicher Parameter angegeben. In diesem Parameter wird eine weitere Funktion, die Rückruffunktion, angegeben. Wenn die asynchrone Funktion zu ihrem Ende kommt ruft sie die diese auf und übergibt ihr das Ergebnis als Parameter.

Durch diese Technik ist es möglich bei jedem Aufruf der asynchronen Funktion eine andere, individuelle Behandlung des Ergebnisses zu realisieren.

Die Transformation von synchronen zu asynchronen Funktionen ist eigentlich ganz einfach. Man ergänzt die Funktion um den zusätzlichen Parameter und ersetzt alle Return-Anweisungen durch einen Aufruf der Callback-Funktion. Der Aufruf solchermaßen geänderter Funktionen muss natürlich ebenfalls geändert werden.

Als Beispiel hier meine Funktion zum auslesen einer einzelnen Episode vor und nach der Umstellung auf Asynchronität:

function readEpisode(episodeUri) {
   "use strict";
   …
   return episode;
};
function readEpisode(episodeUri, onReadCallback) {
   "use strict";
   …             
   if (onReadCallback && typeof onReadCallback === 'function') {
      onReadCallback(episode);
   }                     
}

Events

Events, also Ereignisse, sind spezielle Konstrukte die an einer oder mehreren Stellen im Code “aufgefangen” werden können. Das bedeutet man definiert ein Funktion als Eventhandler die immer dann ausgeführt wird wenn das gewünschte Event auftritt. Dadurch ist es möglich immer die gleichen Abläufe zu realisieren unabhängig davon wo und wann ein Ereignis ausgelöst wird.

Javascript umfasst eine ganze Reihe vordefinierter Events wie onLoad oder onClick. Für eigene Events kann man hingegen auf Custom Events zurückgreifen. Dieses können benannt werden und sind recht flexible wenn man ihnen Daten mitgeben möchte.

Ich habe mich entschlossen nach dem Speichern von Episoden ein Event auszulösen um so die Playliste immer automatisch aktualisieren zu können:

function writeEpisode(episode, onWriteCallback) {
   "use strict";
   …
   document.dispatchEvent(
      new CustomEvent('writeEpisode', {"detail": {'episode': episode}})
   );
}

Das Dokument löst hier ein Event “writeEpisode” aus. Diesem Event wird die geschriebene Episode beigelegt damit der Eventhandler die Daten zur Verfügung hat:

document.addEventListener('writeEpisode', function (event) {
   var i, episode, episodeUI;
   episode = event.detail.episode;
   …
}, false);

Das Problem bei dieser Lösung liegt im Internet Explorer (einschließlich Version 11). Dieser unterstützt die obige Methode zum erzeugen des Events nicht. Zum Glück gibt es jedoch ein Polyfill das den Constructor nachrüstet.

Promises

Wer mit Callback-Funktionen arbeitet wird schnell feststellen das der Code recht unübersichtlich wird. Insbesondere wenn mehrere asynchrone Operationen ineinander geschachtelt werden entsteht ein ganz schöner Wust an Code. Abhilfe sollen hier Promises schaffen. Wer mehr wissen will dem empfehle ich den Artikel JavaScript Promises There and back again. Wer es lieber auf deutsch mag kann ja mal bei Peter Kröner vorbeischauen.

Umdenken

Abschließend kann man sagen: Es erfordert ganz schön Umdenken um bestehenden Code auf asynchrone Abläufe umzubauen. Wenn man den gedanklichen Sprung aber erst mal gemacht hat ist aber alles recht einleuchtend.