XmlHttpRequest und die Worker API

Veröffentlicht am 18. Januar 2011

Ein Gastbeitrag von Rodney Rehm

Browserscope liefert uns eine schöne Übersicht über die HTTP-Spielchen unserer Browser. Leider fehlt ein Test. Nämlich der, der uns mitteilt wie viele XmlHttpRequests (XHR, AJAX) der Browser parallel ausführen kann. Offenbar gehen die Meisten davon aus, dass XmlHttpRequests der Regulierung »Connections per Hostname« unterliegen. Ich übrigens auch - bis letzte Woche.

HTML5 Worker API

Die Worker API erlaubt die reale Parallelisierung von Rechenaufgaben. Wo Worker aus Gründen der Thread-Safety keinen Zugriff auf den DOM haben, dürfen sie aber sehr wohl mit der Außenwelt sprechen. So stellte sich die Frage welchen Regeln diese "wirklich asynchronen XmlHttpRequests" unterliegen würden. Ein kleiner Test sollte ein wenig Licht ins Dunkel bringen. Dabei ist mir aber nicht nicht nur ein einzelnes Licht aufgegangen…

Parallele XmlHttpRequest im window?

Eingangs erwähnt gehen viele Entwickler davon aus, dass so ein aktueller Firefox bis zu 6 XHR-Verbindungen simultan verarbeitet, weil er ja auch 6 HTTP-Verbindungen bedienen kann. Dem ist nicht so. Firefox 3.6 und Chrome 8 verarbeiten immer nur einen einzigen XHR simultan. Nicht so der Safari 5, denn der Held aus Cupertino peitscht auch die XmlHttpRequests mit voller HTTP-Verbindungsbandbreite durch.

XHR in Firefox
XHR in Firefox

Meine Tests waren noch nicht bei der Worker API angekommen, schon hing die Kinnlade auf der Tastatur. Himmel, ich dachte ich wäre WebDeveloper und wüsste was ich mache? Interessant ist auch der Fakt, dass Chrome 8 sich in diesem Punkt wie Firefox 3.6 verhält, wo er doch wie Safari auf Webkit basiert. Das hätte ich nun gar nicht erwartet.

Parallele XmlHttpRequest mit Worker?

Der Test geht weiter. 10 Worker-Instanzen mit jeweils einem XHR stehen nun 10 XHR im normalen window gegenüber. Firefox lässt jeweils 3 XHR aus Workern und 1 XHR aus dem window laufen. Also max. 4 parallele XmlHttpRequests für den Firefox. Safari, der ja schon im window 6 XHR parallel ausführte, zeigt keine Änderung. Chrome verblüfft am meisten. Zum einen ist der Chrom'sche WebInspector nicht in der Lage XmlHttpRequest aus einem Worker im Resources-Tab anzuzeigen. Zum anderen führt er 5 Worker XHR und einen window XHR simultan aus. Also max. 6 parallele XmlHttpRequests für den Chrome.

Firefox führt 4 XHR simultan aus
Firefox: Parallele XmlHttpRequests mit Worker

Erstes Fazit: Mittels den neuen Workern können im Firefox 3.6 max. 4 XmlHttpRequests parallel ausgeführt werden, im Chrome 8 sogar 6 XHR simultan.

Parallele XmlHttpRequest und »Connections per Hostname«?

Nun stellt sich noch die Frage wie sich diese XmlHttpRequests auf die »Connections per Hostname« Restriktion auswirken. Firefox 3.6, Safari 5 und Chrome 8 erlauben jeweils 6 HTTP Verbindungen zum selben Host. Was passiert, wenn 10 XmlHttpRequests und 10 Bilder geladen werden sollen?

Wie zu erwarten war bleibt es auch beim Einsatz der Worker API bei max. 6 HTTP Verbindungen pro Host. Wie mit den unterschiedlichen HTTP-Anfragen umgegangen wird, stellt aber schon die nächste interessante Beobachtung dar. Werden die 10 Bilder und 10 XHR im window angefragt, scheint Safari die XmlHttpRequests zu bevorzugen. Das könnte an der Anfragereihenfolge liegen: ein XHR, ein Bild, ein XHR, ein Bild, […]. Verschieben wir die 10 XHR in Worker, scheinen sich die Anfragen etwas mehr zu vermischen.

Firefox unterliegt der 1 XHR pro window Limitierung. Deshalb werden die 10 Bilder, von denen jeweils 5 neben einem XHR geladen werden können, augenscheinlich bevorzugt. Aber auch im Worker Kontext haben die XHR eindeutig das nachsehen. Reguläre HTTP-Anfragen scheinen eine höhere Priorität als XmlHttpRequest zu besitzen.

Chrome verhält sich beim window wie der Firefox. 1 XHR und 5 Bilder füllen die 6 verfügbaren HTTP-Verbindugnen. Setzen wir die Worker ein, vermischen sich die Anfragen wie auch beim Firefox. Allerdings scheinen nun die XmlHttpRequests bevorzugt zu werden.

Normale HTTP Anfragen und XmlHttpRequests gleichzeitig angstoßen, XHR wird benachteiligt
Firefox: Bilder und XHR

Zweites Fazit: Worker erhöhen zwar die Anzahl parallel ausgeführter XmlHttpRequests, sind aber immer noch an die Limitierung von 6 HTTP-Verbindungen pro Host gebunden.

Zusammenfassung

Wer in seiner AJAX-Applikation unbedingt mehrere XmlHttpRequests ausführen muss, sollte sich die Worker API etwas genauer anschauen – aber übertreibt das Spielchen nicht. Irgendwann werden die Resultate der parallelen XHR wieder in das window gebracht und ins DOM geschossen. Hier kann es schnell zu Engpässen kommen, wenn die DOM-Interaktionen entsprechend schwergewichtig sind.

Über den Autor

Rodney Rehm ist Jahrgang ‘84, hat seine Wurzeln nahe der schweizer Grenze, ist im Internet eher als „globe“ bekannt, und führt seit 2008 eine kleine Agentur am Bodensee. Und das ist auch gut so. Er ist WebDeveloper mit den Schwerpunkten PHP, SQL und Javascript und spielt gerne mit absurden Ideen zur Geschwindigkeitsoptimierung herum. Mit dem Internet beschäftigte er sich seit seines zweijährigen Besuchs einer amerikanischen Privatschule Mitte der 90er. Sein Studium brachte er, wie so viele Webdeveloper, wegen mangelnder Zeit nicht zu Ende. Wo das Interesse seit Anbeginn das Backend war, drang Javascript und somit das Frontend Mitte des letzten Jahrzents immer mehr in den Vordergrund. Er steht für ungepflegte private Webseiten, minimalistische geschäftliche Seiten und twittert gelegentlich.

Kategorie- und Archiv-Dropdowns mit unobtrusive JavaScript

Veröffentlicht am 14. Dezember 2010

Eine gute Website funktioniert, wenn ein Benutzer mit deaktiviertem JavaScript vorbeikommt, genau so gut wie mit aktiviertem Scripting. Man trennt die JS-Schicht der Website sauber von allem anderen (und verzeichtet zum Beispiel auf onclick-Handler im HTML) und legt seine Scripts so an, dass nur die schon vorhandene Funktionalität der Seite verbessern und umformen. So kommen die Besucher sowohl mit als auch ohne JS an ihr Ziel, wenn der Weg ohne Scripting vielleicht etwas mühsamer oder weniger schön ausfällt. Diese Herangehensweise an Scripting auf Websites nennt sich Unobtrusive JavaScript und warum das eine gute Sache ist, erklärt Jenn Lukas in diesem Talk von der JSConf 2010. Neben all den guten Gründen ist das wichtigste Argument für unobtrusive JavaScript, dass es bei entsprechender Planung so einfach umzusetzen ist, dass es grundlos verschenktes Potenzial wäre wenn man darauf verzichten würde. Problematisch kann es allein dann werden, wenn das einem das für die Website verwendete CMS einem einen Strich durch die Rechnung macht – womit wir beim Thema WordPress wären.

Gelegentlich möchte man in seiner Blog-Sidebar Dropdowns-Menüs (select-Elemente) zur Navigation der Kategorien und des Archivs unterbringen und der WordPress-Codex bietet in den Dokumentationen auch entsprechende Lösungen für Kategorien und Archiv an, die allerdings beide nur mit aktiviertem JavaScript funktionieren. In beiden Fällen wird die URL der Ziel-Seite im value-Wert der im Dropdown-Menü verbauten option-Elemente gespeichert und sobald man einen Eintrag auswählt, wird man via JavaScript auf eben jene URL weitergeleitet. Die fast gleiche Funktionalität ließe sich aber auch ganz ohne Scripts erreichen:

  1. Ein altmodisches GET-Formular mit Absendebutton anlegen, das an die Basisadresse der Website sendet
  2. Das select in eben dieses Formular stecken und name="cat" angeben
  3. Den option-Elementen als value den Slug der Kategorien geben

Das Absenden des Formulars resultiert dann in einem Aufruf von blog.de/?cat=kategorie und damit der gewünschten Kategorie, ggf. mit einem Redirect auf die suchmaschinenfreundliche Variante der URL. Das funktioniert für den Besucher genau so gute wie die Lösung aus dem Codex, sieht man einmal davon ab, dass man einen Button anklicken muss. Doch genau hier kann man mit JavaScript eingreifen, den Button verstecken und das Absenden des Formulars über einen via Script eingefügten onclick-Handler übernehmen. Das Endresultat sieht dann für Surfer mit Script wie die Codex-Lösung aus und alle anderen kommen dann eben über einen Klick auf den Button an ihr Ziel und alle sind zufrieden. Also an die Arbeit!

Die Grundlage für das Kategorie-Dropdown bildet ein ganz normales HTML-Formular:

<form id="kategorienform" action="<?php bloginfo('url'); ?>" method="get">
    <label for="kategorienselect">Zu Kategorie springen</label>
    <select id="kategorienselect" name="cat">
        <option value="">-- Bitte auswählen</option>
        <?php
            $categories = get_categories('hierarchical=0');
            foreach($categories as $category){
                $selected = (is_category($category->cat_ID)) ? 'selected' : '';
                echo '<option '.$selected.' value="'.$category->cat_ID.'">'.$category->cat_name.' ('.$category->count.')</option>';
            }
        ?>
    </select>
    <input id="kategorienbutton" value="Kategorie abrufen" type="submit">
</form>

Dieses könnte man so in die Seitenleiste des Dokuments werfen und es würde funktionieren, doch wir wollen ja noch den Absenden-Button loswerden und die Umleitung direkt bei der Auswahl einer Kategorie starten. Hierzu braucht es nicht mehr als 6 Zeilen JavaScript, die man direkt unter dem Formular einfügen kann:

<script type="text/javascript">
    document.getElementById('kategorienselect').onchange = function(){
        if(this.value){
            document.getElementById('kategorienform').submit();
        }
    };
    document.getElementById('kategorienbutton').style.display = 'none';
</script>

Ändert der Benutzer seine Auswahl im select-Element, wird (sofern nicht gerade der „Bitte auswählen“-Eintrag gewählt wird) das Formular automatisch abgesendet, auch ohne den mit style.display = 'none' unsichtbar gemachten Button aktivieren zu müssen – und fertig ist das benutzerfreundliche und trotzdem auch ohne JavaScript funktionierende Kategorie-Dropdown! Das ist zwar etwas mehr Code als die drei Zeilen aus dem Codex, funktioniert dafür aber garantiert bei jedem Besucher. Und das Prinzip ist recht simpel, vorausgesetzt man plant von vornherein entsprechend. Da eben das bei WordPress selbst nicht passiert ist, müssen wir etwas mehr tippen – beziehungsweise im Falle des Archiv-Dropdowns sogar sehr viel mehr:

<form id="archivform" action="<?php bloginfo('url'); ?>" method="get">
    <label for="archivselect">Zu Monat springen</label>
    <select id="archivselect" name="m">
        <option value="">-- Bitte auswählen</option>
        <?php
            $query = "SELECT YEAR(post_date) AS `year`, MONTH(post_date) AS `month`, COUNT(ID) as `posts`
                FROM $wpdb->posts
                WHERE post_type = 'post' AND post_status = 'publish'
                GROUP BY YEAR(post_date), MONTH(post_date)
                ORDER BY post_date DESC";
            $key = md5($query);
            $cache = wp_cache_get('select_archives', 'general');
            if(!isset($cache[$key])){
                $arcresults = $wpdb->get_results($query);
                $cache[$key] = $arcresults;
                wp_cache_set('select_archives', $cache, 'general');
            }
            else{
                $arcresults = $cache[$key];
            }
            if($arcresults){
                global $wp_locale;
                foreach((array) $arcresults as $arcresult){
                    $value = $arcresult->year.$arcresult->month;
                    $text = sprintf(__('%1$s %2$d'), $wp_locale->get_month($arcresult->month), $arcresult->year);
                    $count = '&nbsp;('.$arcresult->posts.')';
                    $selected = (is_month() && get_query_var('year').get_query_var('monthnum') == $value) ? 'selected' : '';
                    echo '<option '.$selected.' value="'.$value.'">'.$text.$count.'</option>';
                }
            }
        ?>
    </select>
    <input id="archivbutton" value="Archiv abrufen" type="submit">
</form>

Das Problem beim Monatsarchiv ist, dass man ohne einen eigenen SQL-Query nicht an die Informationen herankommt, die man für ein Monats-Archiv braucht. So kommt man nicht drum herum, manuell die Datenbank zu bemühen und das Ergebnis händisch zu cachen. Das Prinzip ist aber identisch: einfach ein herkömmliches Formular ausgeben und wieder mit sechs kleinen Zeilen JavaScript aufwerten:

<script type="text/javascript">
    document.getElementById('archivselect').onchange = function(){
        if(this.value){
            document.getElementById('archivform').submit();
        }
    };
    document.getElementById('archivbutton').style.display = 'none';
</script>

Wir fassen zusammen: viele Zeilen Code, aber kompliziert ist das Prinzip nicht. Unobtrusive JavaScript ist eine Frage der intelligenten Planung und wenn die mal, wie bei WordPress, nicht von Haus aus mitgeliefert wird, muss man sich eben behelfen. Bei herkömmlichen Websites gibt es keinen Grund, die Bedienung komplett von JavaScript abhängig zu machen und falls doch mal etwas Mehraufwand entsteht, so ist er die saubere Trennung der Schichten und den Zugänglichkeits-Bonus allemal wert.

Noch mehr Fragen zu HTML5 beantwortet

Veröffentlicht am 13. Dezember 2010

Wie schon im ersten Teil wird es mal wieder Zeit, dass ich ein paar von den HTML5-Fragen, die über nicht-öffentliche Kanäle an mich herangetragen werden nochmal für die Allgemeinheit beantworte. Wie immer gilt: wer mehr Fragen von der Sorte hat, nur her damit! Mich kann man jederzeit via Mail oder Twitter kontaktieren.

Mathematische Formeln in HTML5?

Gibt es mit HTML5 eine Möglichkeit mathematische Gleichungen korrekt darzustellen, so dass kein Plugin mehr nötig ist?

Die gibt es! In HTML5 wird das <math>-Element eingeführt, mit dem man MathML in HTML-Dokumenten verwenden kann. MathML (Mathematical Markup Language) ist ein XML-Format, das durch das W3C spezifiziert wird und das so langsam (ganz langsam) auch auch von den diversen Browsern unterstützt wird. Und in HTML5 kann man ganz einfach das Mathe-XML in herkömmliches HTML stecken:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>MathML</title>
    </head>
    <body>
        <math xmlns="http://www.w3.org/1998/Math/MathML">
            <mrow>
                <mi>a</mi>
                <mo>&#x2062;</mo>
                <msup>
                    <mi>x</mi>
                    <mn>2</mn>
                </msup>
                <mo>+</mo>
                <mi>b</mi>
                <mo>&#x2062;</mo>
                <mi>x</mi>
                <mo>+</mo>
                <mi>c</mi>
            </mrow>
        </math>
    </body>
</html>

Voraussetzung für die korrekte Darstellung ist allerdings ein moderner Browser mit aktiviertem HTML5-Parser (siehe auch: Wozu braucht mein Browser eigentlich einen HTML5-Parser?).

Bilder aus Canvas speichern?

Kann man den Inhalt eines Canvas-Elements irgendwie als Grafik abspeichern?

Da gibt es gleich zwei Möglichkeiten: Erstens ist die Bitmap eines Canvas-Elements im Prinzip nichts weiter als ein herkömmliches Bild im Browser und entsprechend kann man es einfach mit Rechtsklick → „Speichern unter“ abspeichern. Zweitens gibt es mit toDataURL() eine Methode für das Canvas-Element (nicht den 2D-Context), mit der sich der Inhalt der Bitmap in Form eines Base64-codierten PNGs exportieren lässt. Den kann man dann abspeichern oder als src eines img-Elements direkt in das DOM der Website einfügen.

Wohin mit Fragen?

Gibts gute Webforen oder MLs für HTML5-Anwenderfragen? Im Usenet wird man angepöbelt, und Sachverstand ist auch eher nicht da.

Die Erfinder des Ganzen haben für alles gesorgt – die WHATWG bietet ein Forum speziell für Anwender. Ebenfalls empfehlenswert ist der HTML5 Doctor, wo Anwenderprobleme in längeren Postings besprochen und auch entgegengenommen werden.

Auch ich beantworte eure Fragen zu HTML5 gerne! Einfach eine E-Mail schreiben oder Formspring bemühen und ein bisschen Geduld haben – falls ich gerade unterwegs bin, kann es mit Antwort manchmal etwas dauern, doch früher oder später schreibe ich garantiert zurück.

Stoyan Stefanov: JavaScript Patterns (Review)

Veröffentlicht am 29. November 2010

JavaScript-Patterns

Wenn man weiß, dass Stoyan Stefanov seine Brötchen bei Yahoo verdient, kann man schon erahnen, dass sein Buch JavaScript Patterns wohl kaum ein tiefer Griff ins Klo sein dürfte – und das auch nicht der Fall. Auf etwas über 200 Seiten geht es um Entwurfsmuster für JavaScript und das, was dort zu Papier gebracht wurde (ein Mix aus Best Practices und Codeschnipseln), hat sachlich absolut Hand und Fuß. Von grundlegenden Dingen wie Finger weg von new Array() über clevere Tricks mit Funktionen bis zu einem von A bis Z durchexerzierten Modulsystem ist eigentlich alles geboten und auch viele Antipatterns werden gezeigt und erklärt. Also, ein Super-Buch das man sich ohne großes Nachdenken sofort ins Regal stellen sollte? Das kommt ganz drauf an.

Das Buch dürfte für all diejenigen interessant sein, die alle JavaScript-Basics soweit auf dem Kasten haben, dass sie alle Aufgaben des täglichen Webworkertums im Schlaf erledigen und die in Zukunft planen, mehr als nur eben jene Standardaufgaben in Angriff zu nehmen. Für alle, die tendenziell eher JavaScript-Anfänger sind, bietet das Buch zu wenig Grundlagenarbeit. Für alle, die auch in Zukunft nicht vorhaben, mehr als ein paar jQuery-Animationen zusammenzuhauen, gibt es zu wenig Praxisbezug. Das Buch enthält zwar ein Kapitel über DOM-Patterns, aber falls man bereits eine JavaScript-Bibliothek verwendet (und wer tut das nicht?), wird man diese nicht wirklich brauchen. Und wer schon ein ausgewachsener JavaScript-Super-Nerd ist, dürfte sich ein wenig langweilen, denn bahnbrechend Neues werden auch nicht präsentiert. Um das Ganze grafisch darzustellen:

JavaScript Patterns kaufen oder nicht kaufen?

Das ist dem Buch nicht zum Vorwurf zu machen – es ist eben inhaltlich so konzipiert. An dem was drin steht ist absolut nichts auszusetzen, die Frage ist nur, ob der Inhalt für Jeden relevant ist. Wer noch gar kein JS kann oder mit seinen Scripts bis auf weiteres nur Websites zu dekorieren gedenkt, braucht es eher nicht. Alle anderen erhalten ein Büchlein, in dem vielleicht nicht viel Neues steht, aber das in jedem Fall zumindest ein taugliches Nachschlagewrk abgibt – und wenn man bedenkt, was für einen Bockmist eine Google-Suche nach beliebigen JavaScript-Themen zutage fördert, ist das eigentlich schon viel wert.