WinJS unter Windows 8.1

Erstellen von effizienteren Windows Store-Apps mithilfe von JavaScript: Fehlerbehandlung

Eric Schmidt

Ob Sie es glauben oder nicht, manchmal schreiben App-Entwickler Code, der nicht funktioniert. Oder der Code funktioniert zwar, ist aber furchtbar ineffizient und beansprucht viel Speicher. Noch schlimmer ist, dass ineffizienter Code zu einer schlechten Benutzeroberfläche führt. Das Ergebnis sind verärgerte Benutzer, die die App deinstallieren und negative Kritiken verfassen.

Ich möchte auf häufige Probleme mit der Leistung und der Effizienz eingehen, auf die Sie stoßen können, wenn Sie Windows Store-Apps mit JavaScript erstellen. In diesem Artikel werfe ich einen Blick auf bewährte Methoden zur Fehlerbehandlung unter Verwendung der Windows-Bibliothek für JavaScript (WinJS). In einem Folgeartikel werde ich Techniken zum Ausführen von Aufgaben ohne eine Blockierung des UI-Threads besprechen, insbesondere Web-Worker und die neue WinJS.Utilities.Scheduler-API in WinJS 2.0, wie sie in Windows 8.1 verfügbar ist. Ich stelle auch das neue vorhersagbare Lebenszyklusmodell von Objekten in WinJS 2.0 vor und gehe dabei speziell darauf ein, wann und wie Steuerelemente entfernt werden.

Drei Aspekte stehen bei allen Themen im Mittelpunkt:

  • Fehler oder Ineffizienzen, die in einer mit JavaScript erstellten Windows Store-App auftreten können
  • Diagnosetools, um diese Fehler und Ineffizienzen zu ermitteln
  • WinJS-APIs, Features und bewährte Methoden, die bestimmte Probleme beheben können

Ich stelle einigen bewusst fehlerhaften Code bereit. Aber Sie können sicher sein, dass ich im Code darauf hinweise, dass etwas nicht funktioniert oder nicht funktionieren soll.

Für diese Demos verwende ich Visual Studio 2013, Windows 8.1 und WinJS 2.0. Viele der von mir verwendeten Diagnosetools sind in Visual Studio 2013 verfügbar. Wenn Sie die aktuellen Versionen der Tools nicht heruntergeladen haben, können Sie sie im Windows Dev Center abrufen (bit.ly/K8nkk1). Prüfen Sie regelmäßig, ob Updates für Visual Studio verfügbar sind, denn diese umfassen neue Diagnosetools.

Ich gehe davon aus, dass Sie mit dem Erstellen von Windows Store-Apps mithilfe von JavaScript gut vertraut sind. Wenn die Plattform noch relativ neu für Sie ist, schlage ich vor, dass Sie mit dem grundlegenden Beispiel „Hello World“ (bit.ly/vVbVHC) oder mit dem etwas anspruchsvolleren Hilo-Beispiel für JavaScript (bit.ly/SgI0AA) beginnen.

Einrichten des Beispiels

Ich erstelle zuerst ein neues Projekt in Visual Studio 2013. Dazu verwende ich die Vorlage „Navigations-App“, die ein guter Ausgangspunkt für eine grundlegende mehrseitige App ist. Ich füge der Seite „default.html“ im Stamm der Projektmappe außerdem ein NavBar-Steuerelement (bit.ly/14vfvih) hinzu, das den in der Vorlage bereitgestellten AppBar-Code ersetzt. Da ich mehrere Konzepte, Diagnosetools und Programmiertechniken vorführen möchte, füge ich der App für jede Demo eine neue Seite hinzu. Das erleichtert die Navigation zwischen all den Testfällen für mich.

Abbildung 1 zeigt das vollständige HTML-Markup für das NavBar-Steuerelement. Wenn Sie das Beispiel nachvollziehen möchten, kopieren Sie diesen Code, und fügen Sie ihn in Ihre Projektmappe ein.

Abbildung 1: Das NavBar-Steuerelement

    <!-- The global navigation bar for the app. -->
    <div id="navBar" data-win-control="WinJS.UI.NavBar">
      <div id="navContainer" 
           data-win-control="WinJS.UI.NavBarContainer">
        <div id="homeNav" 
          data-win-control="WinJS.UI.NavBarCommand"
          data-win-options="{
            location: '/pages/home/home.html',
            icon: 'home',
            label: 'Home page'
        }">
        </div>
        <div id="handlingErrors"
          data-win-control="WinJS.UI.NavBarCommand"
          data-win-options="{
            location: '/pages/handlingErrors/handlingErrors.html',
            icon: 'help',
            label: 'Handling errors'
        }">
        </div>
        <div id="chainedAsync"
          data-win-control="WinJS.UI.NavBarCommand"
          data-win-options="{
            location: '/pages/chainedAsync/chainedAsync.html',
            icon: 'link',
            label: 'Chained asynchronous calls'
        }">
        </div>
      </div>
    </div>

Weitere Informationen über das Erstellen von Navigationsleisten finden Sie in einigen Artikeln von Rachel Appel in der Reihe „Moderne Apps“, zum Beispiel unter msdn.microsoft.com/magazine/dn342878.

Sie können dieses Projekt allein mit der Navigationsleiste ausführen, wobei allerdings durch einen Klick auf eine Navigationsschaltfläche eine Ausnahme in „navigator.js“ ausgelöst wird. Ich komme später in diesem Artikel auf die Behandlung von Fehlern zurück, die in „navigator.js“ auftreten. Berücksichtigen Sie vorläufig, dass die App immer auf der Startseite startet und dass Sie einen Rechtsklick auf die App ausführen müssen, um die Navigationsleiste anzuzeigen.

Behandeln von Fehlern

Natürlich ist die beste Methode zur Fehlervermeidung die Veröffentlichung von Apps, die keine Fehler verursachen. In einer perfekten Welt würden alle Entwickler perfekten Code schreiben, der nie abstürzt und nie eine Ausnahme auslöst, aber diese perfekte Welt gibt es nicht.

So sehr die Benutzer vollständig fehlerfreie Apps bevorzugen, so gut sind sie auch darin, neue und kreative Wege zu beschreiten, mit denen Apps fehlschlagen – Wege, die Ihnen nie in den Sinn gekommen sind. Aus diesem Grund müssen Sie eine stabile Fehlerbehandlung in die Apps integrieren.

Fehler in Windows Store-Apps, die mit JavaScript und HTML erstellt wurden, verhalten sich genau wie Fehler auf normalen Webseiten. Wenn ein Fehler in einem Objekt des Dokumentobjektmodells (Document Object Model, DOM) auftritt, welches eine Fehlerbehandlung zulässt (wie beispielsweise die Elemente <script>, <style> oder <img>), wird für dieses Objekt das onerror-Ereignis ausgelöst. Bei Fehlern in der JavaScript-Aufrufliste wird der Fehler in der Aufrufkette nach oben weitergegeben, bis er abgefangen wird (zum Beispiel in einem Try-Catch-Block), oder bis er das Fensterobjekt erreicht, wo er das window.onerror-Ereignis auslöst.

WinJS stellt Möglichkeiten zur Fehlerbehandlung für Code auf mehreren Ebenen zur Verfügung, zusätzlich zu dem, was die Microsoft-Webplattform bereits für normale Webseiten bereitstellt. Auf einer grundlegenden Ebene löst jeder Fehler, der nicht in einem Try-Catch-Block oder dem auf ein WinJS.Promise-Objekt angewendeten onError-Handler abgefangen wird (zum Beispiel in einem Aufruf der then- oder done-Methoden), das WinJS.Application.onerror-Ereignis aus. Ich gehe darauf gleich noch ein.

In der Praxis können Sie zusätzlich zu „Application.onerror“ auf anderen Ebenen auf Fehler warten. Mit WinJS und den Vorlagen, die Visual Studio bereitstellt, können Sie Fehler auch auf der Seitensteuerelement-Ebene und der Navigationsebene behandeln. Wird ein Fehler ausgelöst, während die App zu einer Seite navigiert und diese lädt, löst der Fehler die Fehlerbehandlung auf Navigationsebene aus, anschließend die Fehlerbehandlung auf Seitenebene und zum Schluss die Fehlerbehandlung auf Anwendungsebene. Sie können den Fehler auf der Navigationsebene abbrechen, aber alle auf den Handler für Seitenfehler angewendeten Ereignishandler werden dennoch ausgelöst.

In diesem Artikel betrachte ich alle Fehlerbehandlungsebenen, und ich beginne mit der wichtigsten: dem Application.onerror-Ereignis.

Fehlerbehandlung auf Anwendungsebene

WinJS stellt das WinJS.Application.onerror-Ereignis bereit (bit.ly/1cOctjC), mit dem Ihre App eine grundlegende Verteidigungslinie gegen Fehler erhält. Es übernimmt alle Fehler, die von „window.onerror“ abgefangen werden. Es fängt auch Fehler von Zusagen ab und alle Fehler, die bei der Verwaltung von App-Modell-Ereignissen auftreten. Sie können zwar dem window.onerror-Ereignis in der App einen Ereignishandler hinzufügen, praktischer ist es für Sie aber, nur „Application.onerror“ zu verwenden und eine einzige Warteschlange mit Ereignissen zu überwachen. 

Wenn der Application.onerror-Handler einen Fehler abfängt, müssen Sie entscheiden, wie Sie mit dem Fehler umgehen. Es gibt mehrere Optionen:

  • Warnen Sie die Benutzer im Falle von kritischen, blockierenden Fehlern mit einem Meldungsdialogfeld. Kritisch ist ein Fehler, der sich auf die weitere Ausführung der App auswirkt und möglicherweise eine Benutzereingabe erfordert, damit der Benutzer fortfahren kann.
  • Warnen Sie die Benutzer im Falle von Fehlern zur Information oder nicht blockierenden Fehlern (zum Beispiel Fehler beim Synchronisieren oder Abrufen von Onlinedaten) durch ein Flyout oder eine Inlinemeldung.
  • Bei Fehlern, die sich nicht auf die Benutzererfahrung auswirken, fangen Sie den Fehler unbemerkt ab.
  • In den meisten Fällen schreiben Sie den Fehler in ein Ablaufverfolgungsprotokoll (insbesondere in eines, das mit einem Analysemodul verknüpft ist), damit Sie Telemetrieinformationen von Kunden sammeln können. Verfügbare Analyse-SDKs finden Sie im Windows-Diensteverzeichnis unter services.windowsstore.com, indem Sie in der Liste links unter „By service type“ auf „Analytics“ klicken.

In diesem Beispiel bleibe ich bei Meldungsdialogfeldern. Ich öffne „default.js“ („/js/default.js“), und ich füge der anonymen Hauptfunktion unter dem Handler für das app.oncheckpoint-Ereignis den Code hinzu, der in Abbildung 2 dargestellt ist.

Abbildung 2: Hinzufügen eines Meldungsdialogfelds

app.onerror = function (err) {
  var message = err.detail.errorMessage ||
    (err.detail.exception && err.detail.exception.message) ||
    "Indeterminate error";
  if (Windows.UI.Popups.MessageDialog) {
    var messageDialog =
      new Windows.UI.Popups.MessageDialog(
        message,
        "Something bad happened ...");
    messageDialog.showAsync();
    return true;
  }
}

In diesem Beispiel zeigt der Fehlerereignishandler eine Meldung an, die dem Benutzer mitteilt, dass ein Fehler und welcher Fehler aufgetreten ist. Der Ereignishandler gibt TRUE zurück, um das Meldungsdialogfeld weiterhin anzuzeigen, bis der Benutzer es schließt. (Mit dem Zurückgeben von TRUE wird auch der Prozess „WWAHost.exe“ informiert, dass der Fehler behandelt wurde und der Prozess fortgesetzt werden kann.)

Ich erstelle jetzt einige Fehler, die dieser Code behandeln soll. Ich erstelle einen benutzerdefinierten Fehler, löse den Fehler aus und fange ihn dann im Ereignishandler ab. Im ersten Beispiel füge ich dem Ordner „pages“ einen neuen Ordner namens „handlingErrors“ hinzu. Ich füge im Ordner ein neues Seitensteuerelement hinzu, indem ich im Projektmappen-Explorer mit der rechten Maustaste auf das Projekt klicke und „Hinzufügen | Neues Element“ auswähle. Wenn ich das handlingErrors-Seitensteuerelement zum Projekt hinzufüge, stellt Visual Studio drei Dateien im Ordner „handlingErrors“ („/pages/handlingErrors“) bereit: „handlingErrors.html“, „handlingErrors.js“ und „handlingErrors.css“.

Ich öffne „handlingErrors.html“ und füge im <section>-Tag des HTML-Textkörpers dieses einfache Markup hinzu:

    <!-- When clicked, this button raises a custom error. -->
    <button id="throwError">Throw an error!</button>

Als Nächstes öffne ich „handlingErrors.js“ und füge der Schaltfläche in der ready-Methode des PageControl-Objekts einen Ereignishandler hinzu, wie in Abbildung 3 gezeigt. Für den Kontext habe ich die vollständige PageControl-Definition in „handlingErrors.js“ bereitgestellt.

Abbildung 3: Definition des handlingErrors-PageControl-Objekts

// For an introduction to the Page Control template, see the following documentation:
// https://go.microsoft.com/fwlink/?LinkId=232511
(function () {
  "use strict";
  WinJS.UI.Pages.define("/pages/handlingErrors/handlingErrors.html", {
    ready: function (element, options) {
      // ERROR: This code raises a custom error.
      throwError.addEventListener("click", function () {
        var newError = new WinJS.ErrorFromName("Custom error", 
          "I'm an error!");
        throw newError;
      });
    },
    unload: function () {
      // Respond to navigations away from this page.
      },
    updateLayout: function (element) {
      // Respond to changes in layout.
    }
  });
})();

Ich drücke jetzt F5, um das Beispiel auszuführen, navigiere zur handlingErrors-Seite und klicke auf die Schaltfläche „Throw an error!“. (Wenn Sie dies nachvollziehen, sehen Sie ein Dialogfeld von Visual Studio mit der Mitteilung, dass ein Fehler ausgelöst wurde. Klicken Sie auf „Weiter“, um das Beispiel weiterhin auszuführen.) Anschließend wird ein Meldungsdialogfeld mit dem Fehler angezeigt, wie in Abbildung 4 gezeigt.

The Custom Error Displayed in a Message Dialog
Abbildung 4: Meldungsdialogfeld mit dem benutzerdefinierten Fehler

Benutzerdefinierte Fehler

Das Application.onerror-Ereignis hat bestimmte Erwartungen im Hinblick auf den Fehler, den es behandelt. Zum Erstellen eines benutzerdefinierten Fehlers verwenden Sie am besten das WinJS.ErrorFromName-Objekt (bit.ly/1gDESJC). Das erstellte Objekt macht eine Standardschnittstelle zum Analysieren für Fehlerhandler verfügbar.

Um einen eigenen benutzerdefinierten Fehler ohne Verwendung des ErrorFromName-Objekts zu erstellen, müssen Sie eine toString-Methode implementieren, die die Meldung des Fehlers zurückgibt.

Andernfalls zeigen sowohl der Visual Studio-Debugger als auch das Meldungsdialogfeld „[Object object]“, wenn der benutzerdefinierte Fehler ausgelöst wird. Beide rufen jeweils die toString-Methode für das Objekt auf, aber da im unmittelbaren Objekt keine solche Methode definiert ist, wird die Prototypvererbungskette durchlaufen, um eine Definition von „toString“ zu finden. Beim Erreichen des primitiven Object-Typs, der eine toString-Methode besitzt, wird diese Methode aufgerufen (die nur Informationen über das Objekt anzeigt).

Fehlerbehandlung auf Seitenebene

Das PageControl-Objekt in WinJS bietet eine weitere Ebene der Fehlerbehandlung für Apps. Wenn ein Fehler während des Ladens einer Seite auftritt, ruft WinJS die IPageControlMembers.error-Methode auf. Nachdem die Seite geladen wurde, werden die Fehler der IPageControlMembers.error-Methode hingegen vom Application.onerror-Ereignishandler aufgenommen, wobei die Fehlermethode der Seite ignoriert wird.

Ich füge dem PageControl-Element, das die handleErrors-Seite darstellt, eine Fehlermethode hinzu. Die Fehlermethode schreibt mithilfe von „WinJS.log“ in die JavaScript-Konsole in Visual Studio. Die Protokollierungsfunktionalität muss erst gestartet werden. Ich muss daher „WinJS.Utilities.startLog“ aufrufen, bevor ich versuche, diese Methode zu verwenden. Außerdem prüfe ich, ob das WinJS.log-Member vorhanden ist, bevor ich es tatsächlich aufrufe.

Der vollständige Code für „handleErrors.js“ („/pages/handleErrors/handleErrors.js“) wird in Abbildung 5 gezeigt.

Abbildung 5: Vollständiger Code für „handleErrors.js“

(function () {
  "use strict";
  WinJS.UI.Pages.define("/pages/handlingErrors/handlingErrors.html", {
    ready: function (element, options) {
      // ERROR: This code raises a custom error.      
      throwError.addEventListener("click", function () {
        var newError = {
          message: "I'm an error!",
          toString: function () {
            return this.message;
          }
        };
        throw newError;
      })
    },
    error: function (err) {
      WinJS.Utilities.startLog({ type: "pageError", tags: "Page" });
      WinJS.log && WinJS.log(err.message, "Page", "pageError");
    },
    unload: function () {
      // TODO: Respond to navigations away from this page.
    },
    updateLayout: function (element) {
      // TODO: Respond to changes in layout.
    }
  });
})();

„WinJS.log“

Der in Abbildung 5 gezeigte Aufruf von „WinJS.Utilities.startLog“ startet die WinJS.log-Hilfsfunktion, die standardmäßig Ausgaben in die JavaScript-Konsole schreibt. Während der Entwurfszeit ist dies eine große Hilfe fürs Debuggen, lässt allerdings nicht zu, dass Fehlerdaten erfasst werden, nachdem Benutzer die App installiert haben.

Erwägen Sie für Apps, die fertig für die Veröffentlichung und Bereitstellung sind, die Erstellung einer eigenen Implementierung von „WinJS.log“, die ein Analysemodul aufruft. Dadurch können Sie Telemetriedaten über die App-Leistung sammeln, um unvorhergesehene Fehler in zukünftigen Versionen Ihrer App zu beheben. Stellen Sie aber sicher, dass die Kunden von der Sammlung der Daten wissen, und listen Sie in der Datenschutzerklärung der App unmissverständlich auf, welche Daten vom Analysemodul gesammelt werden.

Wenn Sie „WinJS.log“ auf diese Weise überschreiben, fängt die WinJS.log-Funktion sämtliche Ausgaben ab, die andernfalls an die JavaScript-Konsole übermittelt würden, zum Beispiel auch Statusupdates vom Planer. Aus diesem Grund müssen Sie beim Aufrufen von „WinJS.Utilities.startLog“ einen aussagekräftigen Namen und Typwert übergeben, damit Sie Fehler ausfiltern können, die Sie nicht berücksichtigen möchten.

Ich versuche jetzt erneut, das Beispiel auszuführen und auf „Throw an error!“ zu klicken. Das Ergebnis ist genau dasselbe Verhalten wie zuvor: Visual Studio nimmt den Fehler auf, und dann wird das Application.onerror-Ereignis ausgelöst. Die JavaScript-Konsole zeigt keine Meldungen für den Fehler an, da der Fehler nach dem Laden der Seite ausgelöst wurde. Daher wurde der Fehler nur vom Application.onerror-Ereignishandler aufgenommen.

Warum verwende ich dann die PageControl-Fehlerbehandlung? Weil sie besonders nützlich ist, was das Abfangen von Fehlern und die Diagnose für Fehler in WinJS-Steuerelementen betrifft, die deklarativ im HTML-Code erstellt werden. Als Beispiel füge ich in den <section>-Tags von „handleErrors.html“ („/pages/handleErrors/handleErrors.html“) unter der Schaltfläche das folgende HTML-Markup hinzu:

    <!-- ERROR: AppBarCommands must be button elements by default
      unless specified otherwise by the 'type' property. -->
    <div data-win-control="WinJS.UI.AppBarCommand"></div>

Ich drücke jetzt F5, um das Beispiel auszuführen, und navigiere zur Seite „handleErrors“. Erneut wird das Meldungsdialogfeld angezeigt, bis es geschlossen wird. Allerdings wird in der JavaScript-Konsole die folgende Meldung angezeigt (Sie müssen zum Desktop zurückwechseln, um dies zu sehen):

pageError: Page: Invalid argument: For a button, toggle, or flyout   command, the element must be null or a button element

Sie sehen, dass die Fehlerbehandlung auf Anwendungsebene angezeigt wird, obwohl ich den Fehler in „PageControl“ behandelt habe (und dieses Element den Fehler protokolliert hat). Wie kann ich also einen Fehler auf einer Seite abfangen, ohne dass er zur Anwendung aufsteigt?

Die beste Methode zum Abfangen eines Fehlers auf Seitenebene besteht im Hinzufügen einer Fehlerbehandlung zum Navigationscode, was ich Ihnen als Nächstes zeige.

Fehlerbehandlung auf Navigationsebene

Beim Ausführen des vorherigen Tests, bei dem der app.onerror-Ereignishandler den Fehler auf Seitenebene behandelte, schien die App auf der Startseite zu bleiben. Aber aus irgendeinem Grund wurde ein Back-Schaltflächen-Steuerelement angezeigt. Durch einen Klick auf die Back-Schaltfläche wurde ich zu einer (deaktivierten) Seite „handlingErrors.html“ geleitet.

Die Ursache hierfür ist der in der Vorlage „Navigations-App“ bereitgestellte Navigationscode in „navigator.js“ („/js/navigator.js“). Dieser Code versucht immer noch, zu der Seite zu navigieren, obwohl diese fehlgeschlagen ist. Des Weiteren navigiert der Code zurück zur Startseite und fügt die fehleranfällige Seite zum Navigationsverlauf hinzu. Darum sehe ich die Back-Schaltfläche auf der Startseite, nachdem ich versucht habe, zu „handlingErrors.html“ zu navigieren.

Um den Fehler in „navigator.js“ abzubrechen, ersetze ich die PageControlNavigator._navigating-Funktion mit dem Code in Abbildung 6. Wie Sie sehen, enthält die Navigationsfunktion einen Aufruf der WinJS.UI.Pages.render-Methode, die ein Promise-Objekt zurückgibt. Die render-Methode versucht, ein neues PageControl-Element aus dem an sie übergebenen URI zu erstellen und in ein Hostelement einzufügen. Da das resultierende PageControl-Element einen Fehler enthält, tritt ein Fehler für die zurückgegebene Zusage auf. Um den während der Navigation aufgetretenen Fehler abzufangen, füge ich dem onError-Parameter der then-Methode, die von diesem Promise-Objekt verfügbar gemacht wird, eine Fehlerbehandlung hinzu. Diese fängt den Fehler ab und verhindert somit, dass er das Application.onerror-Ereignis auslöst.

Abbildung 6: Die PageControlNavigator._navigating-Funktion in „navigator.js“

// Other PageControlNavigator code ...
// Responds to navigation by adding new pages to the DOM.
_navigating: function (args) {
  var newElement = this._createPageElement();
  this._element.appendChild(newElement);
  this._lastNavigationPromise.cancel();
  var that = this;
  this._lastNavigationPromise = WinJS.Promise.as().then(function () {
    return WinJS.UI.Pages.render(args.detail.location, newElement,
       args.detail.state);
  }).then(function parentElement(control) {
    var oldElement = that.pageElement;
    // Cleanup and remove previous element
    if (oldElement.winControl) {
      if (oldElement.winControl.unload) {
        oldElement.winControl.unload();
      }
      oldElement.winControl.dispose();
    }
    oldElement.parentNode.removeChild(oldElement);
    oldElement.innerText = "";
  },
  // Display any errors raised by a page control,
  // clear the backstack, and cancel the error.
  function (err) {
    var messageDialog =
      new Windows.UI.Popups.MessageDialog(
        err.message,
        "Sorry, can't navigate to that page.");
    messageDialog.showAsync()
    nav.history.backStack.pop();
    return true;
  });
  args.detail.setPromise(this._lastNavigationPromise);
},
// Other PageControlNavigator code ...

Zusagen in WinJS

Zum Erstellen und Verketten von Zusagen – und bewährten Methoden dafür – gibt es viele Veröffentlichungen. Daher überspringe ich dieses Thema in diesem Artikel. Weitere Informationen finden Sie bei Bedarf in Kraig Brockschmidts Blogbeitrag unter bit.ly/1cgMAnu oder im Anhang A in seinem kostenlosen E-Book „Programming Windows Store Apps with HTML, CSS, and JavaScript, Second Edition“ (bit.ly/1dZwW1k).

Es ist völlig korrekt, „navigator.js“ zu bearbeiten. Obwohl es von der Visual Studio-Projektvorlage bereitgestellt wird, ist es Teil des Codes Ihrer App und kann Ihrem Bedarf entsprechend geändert werden.

In der _navigating-Funktion habe ich eine Fehlerbehandlung zum letzten Aufruf von „promise.then“ hinzugefügt. Die Fehlerbehandlung zeigt ein Meldungsdialogfeld an, wie bei der Fehlerbehandlung auf Anwendungsebene. Anschließend bricht sie den Fehler ab, indem sie TRUE zurückgibt. Sie entfernt außerdem die Seite aus dem Navigationsverlauf.

Wenn ich das Beispiel erneut ausführe und zu „handlingErrors.html“ navigiere, sehe ich das Meldungsdialogfeld mit der Mitteilung über einen Fehler bei dem Navigationsversuch. Das Meldungsdialogfeld aus der Fehlerbehandlung auf Anwendungsebene wird nicht angezeigt.

Aufspüren von Fehlern in asynchronen Ketten

Beim Erstellen von Apps in JavaScript muss ich häufig eine asynchrone Aufgabe auf eine andere folgen lassen. Dafür erstelle ich Zusagenketten. Verkettete Zusagen durchlaufen weiterhin die Aufgaben, auch wenn eine der Zusagen in der Kette einen Fehler zurückgibt. Eine bewährte Methode besteht darin, eine Kette von Zusagen immer mit einem Aufruf der done-Methode zu beenden. Die done-Funktion löst alle Fehler aus, die in der Fehlerbehandlung für alle vorherigen then-Anweisungen abgefangen worden wären. Das bedeutet, dass ich nicht für jede Zusage in einer Kette Fehlerfunktionen definieren muss.

Auch so kann es schwierig sein, in sehr langen Ketten Fehler aufzuspüren, sobald sie im Aufruf von „promise.done“ abgefangen wurden. Verkettete Zusagen können mehrere Aufgaben enthalten, und jede davon kann fehlschlagen. Ich könnte in jeder Aufgabe einen Haltepunkt festlegen, um zu sehen, wo der Fehler auftritt, aber das wäre furchtbar zeitaufwendig.

An dieser Stelle kommt Visual Studio 2013 zu Hilfe. Das in Visual Studio 2010 eingeführte Aufgabenfenster erhielt ein Upgrade und behandelt nun auch das Debuggen von asynchronem JavaScript. Im Aufgabenfenster können Sie alle aktiven und abgeschlossenen Aufgaben an jeder beliebigen Stelle in Ihrem App-Code sehen.

Für das nächste Beispiel füge ich dem Projekt eine neue Seite hinzu, um dieses herausragende Tool vorzuführen. Ich erstelle in der Projektmappe im Ordner „pages“ einen neuen Ordner namens „chainedAsync“. Dann füge ich ein neues Seitensteuerelement namens „chainAsync.html“ hinzu (wodurch „/pages/­chainedAsync/chainedAsync.html“ und die zugehörigen JS- und CSS-Dateien erstellt werden).

Ich füge in „chainedAsync.html“ innerhalb der <section>-Tags das folgende Markup hinzu:

    <!-- ERROR:Clicking this button starts a chain reaction with an error. -->
    <p><button id="startChain">Start the error chain</button></p>
    <p id="output"></p>

In „chainedAsync.js“ füge ich der ready-Methode für die Seite den in Abbildung 7 gezeigten Ereignishandler für das Klickereignis der startChain-Schaltfläche hinzu.

Abbildung 7: Die Inhalte der PageControl.ready-Funktion in „chainedAsync.js“

startChain.addEventListener("click", function () {
  goodPromise().
    then(function () {
        return goodPromise();
    }).
    then(function () {
        return badPromise();
    }).
    then(function () {
        return goodPromise();
    }).
    done(function () {
        // This *shouldn't* get called
    },
      function (err) {
          document.getElementById('output').innerText = err.toString();
    });
});

Zuletzt definiere ich die in Abbildung 8 gezeigten goodPromise- und badPromise-Funktionen in „chainAsync.js“, damit sie in den PageControl-Methoden verfügbar sind.

Abbildung 8: Die Definitionen der goodPromise- und badPromise-Funktionen in „chainAsync.js“

function goodPromise() {
  return new WinJS.Promise(function (comp, err, prog) {
    try {
      comp();
    } catch (ex) {
      err(ex)
    }
  });
}
// ERROR: This returns an errored-out promise.
function badPromise() {
  return WinJS.Promise.wrapError("I broke my promise :(");
}

Ich führe wieder das Beispiel aus, navigiere zur Seite „Chained asynchronous“, und klicke dann auf „Start the error chain“. Nach kurzem Warten wird unter der Schaltfläche die Meldung „I broke my promise :(“ angezeigt.

Jetzt muss ich herausfinden, wo dieser Fehler auftrat und wie ich ihn beheben kann. (In einer ausgedachten Situation wie dieser weiß ich natürlich genau, wo der Fehler auftrat. Zu Lernzwecken vergesse ich aber, dass der Fehler durch „badPromise“ in die verketteten Zusagen eingefügt wurde.)

Um herauszufinden, wo die verketteten Zusagen misslingen, platziere ich einen Haltepunkt in der Fehlerbehandlung, die im Aufruf von „done“ im Klickhandler für die startChain-Schaltfläche definiert ist, wie in Abbildung 9 gezeigt.

The Position of the Breakpoint in chainedAsync.htmlAbbildung 9: Die Position des Haltepunkts in „chainedAsync.html“

Ich führe denselben Test erneut aus, und wenn ich zu Visual Studio zurückkehre, wurde die Programmausführung am Haltepunkt angehalten. Ich öffne als Nächstes das Aufgabenfenster („Debuggen | Fenster | Aufgaben“), um die derzeit aktiven Aufgaben zu sehen. Die Ergebnisse sind in Abbildung 10 dargestellt.

The Tasks Window in Visual Studio 2013 Showing the ErrorAbbildung 10: Das Aufgabenfenster in Visual Studio 2013, das den Fehler anzeigt

Zunächst sticht in dem Fenster nichts in besonderem Maße als Ursache des Fehlers heraus. Im Fenster sind fünf Aufgaben aufgelistet, die alle als aktiv gekennzeichnet sind. Bei genauerer Betrachtung sehe ich aber, dass eine der aktiven Aufgaben der Planer ist, der Zusagenfehler in die Warteschlange einreiht – und das sieht vielversprechend aus.

(Wenn Sie gern mehr über den Planer erfahren möchten, lesen Sie bitte den nächsten Artikel in dieser Reihe, in dem ich die neue Planungs-API in WinJS erläutern werde.)

Wenn ich mit der Maus auf diese Zeile (ID 120 in Abbildung 10) im Aufgabenfenster zeige, erhalte ich eine ausgerichtete Ansicht der Aufrufliste für diese Aufgabe. Ich sehe mehrere Fehlerhandler und siehe da, „badPromise“ ist nahe am Anfang dieser Aufrufliste. Wenn ich auf diese Zeile doppelklicke, leitet Visual Studio mich direkt zu der Codezeile in „badPromise“, die den Fehler ausgelöst hat. In einem realen Szenario würde ich jetzt durch eine Diagnose ermitteln, warum „badPromise“ einen Fehler ausgelöst hat.

WinJS bietet mehrere Ebenen der Fehlerbehandlung in einer App, über den verlässlichen Try-Catch-Finally-Block hinaus. Eine leistungsfähige App muss einen entsprechenden Grad der Fehlerbehandlung verwenden, damit die Benutzer eine reibungslos funktionierende App erhalten. In diesem Artikel habe ich gezeigt, wie Sie die Fehlerbehandlung auf App-Ebene, Seitenebene und Navigationsebene in eine App integrieren. Sie haben außerdem erfahren, wie Sie einige der neuen Tools in Visual Studio 2013 nutzen können, um Fehler in verketteten Zusagen aufzuspüren.

Im nächsten Artikel dieser Reihe werde ich einige der Techniken zur Verbesserung der Leistung von Windows Store-Apps untersuchen. Der Artikel umfasst Web-Worker, die neue Planungs-API in WinJS 2.0 und das neue Dispose-Muster in WinJS 2.0.

Eric Schmidt ist Inhaltsentwickler im Microsoft Windows Developer Content-Team und schreibt über die Windows-Bibliothek für JavaScript (WinJS). Zuvor war er in der Office Division von Microsoft tätig, wo er Codebeispiele für die Apps für Office-Plattform erstellt hat. Schmidt widmet sich seiner Familie, spielt Kontrabass, erstellt HTML5-Videospiele und verfasst Blogbeiträge über Konstruktionsspielzeug (historybricks.com).

Unser Dank gilt den folgenden technischen Experten von Microsoft für die Durchsicht dieses Artikels: Kraig Brockschmidt und Josh Williams
Kraig Brockschmidt ist Senior Program Manager im Windows Ecosystem-Team, wo er direkt mit der Entwicklercommunity und wichtigen Partnern an der Entwicklung von Windows Store-Apps zusammenarbeitet. Er ist der Verfasser von „Programming Windows Store Apps in HTML, CSS, and JavaScript“ (jetzt als zweite Ausgabe), und er veröffentlicht weitere Einblicke unter http://www.kraigbrockschmidt.com/blog.

Josh Williams ist Principal Software Development Engineer Lead im Windows Developer Experience-Team. Williams und sein Team entwickeln die Windows-Bibliothek für JavaScript (WinJS).