Windows Dev Center

Muster und Tipps für die asynchrone Programmierung in Hilo (Windows Store-Apps mit JavaScript und HTML)

Aus: Umfassende Entwicklung einer Windows Store-App mit JavaScript: Hilo

Leitfaden-Logo

Vorherige Seite | Nächste Seite

Hilo verwendet Zusagen zur Verarbeitung der Ergebnisse asynchroner Vorgänge. Zusagen stellen das erforderliche Muster für die asynchrone Programmierung in Windows Store-Apps mit JavaScript dar. In diesem Dokument finden Sie eine Reihe von Tipps und Richtlinien zur Verwendung von Zusagen. Außerdem enthält das Dokument Beispiele für die verschiedenen Möglichkeiten zur Verwendung von Zusagen und zur Erstellung von Zusagenketten in Ihrer App.

Download

Herunterladen des Hilo-Beispiels
Buch herunterladen (PDF)

Anweisungen zu dem heruntergeladenen Code finden Sie unter Erste Schritte mit Hilo.

Im Einzelnen werden Sie Folgendes lernen:

  • Erstellen von asynchronem Code
  • Verwenden von Zusagen
  • Verketten und Gruppieren von Zusagen
  • Codieren von Zusagenketten zur Vermeidung von Schachtelungen
  • Umbrechen von Werten, die keine Zusage darstellen, in einer Zusage
  • Behandeln von Fehlern in einer Zusage

Betrifft

  • Windows-Runtime für Windows 8
  • WinJS
  • JavaScript

Eine kurze Einführung in Zusagen

Um die asynchrone Programmierung in JavaScript zu unterstützen, implementieren Windows-Runtime und WinJS die Common JS Promises/A-Spezifikation. Bei einer Zusage handelt es sich um ein Objekt, das für einen Wert steht, der erst später verfügbar sein wird. Die Windows-Runtime und WinJS brechen die meisten APIs in einem Zusagenobjekt um, um asynchrone Methodenaufrufe und die asynchrone Programmierung zu unterstützen.

Bei Verwendung einer Zusage können Sie die then-Methode für das zurückgegebene Zusagenobjekt aufrufen, um die Handler für Ergebnisse oder Fehler zuzuordnen. Der erste an then übergebene Parameter gibt die Rückruffunktion (den Vervollständigungshandler) an, die ausgeführt wird, wenn die Zusage ohne Fehler abgeschlossen wird. Im Folgenden finden Sie ein einfaches Beispiel in Hilo, das eine Funktion angibt, die beim Abschließen des asynchronen Aufrufs (gespeichert in queryPromise) ausgeführt werden soll. In diesem Beispiel wird das Ergebnis des asynchronen Aufrufs an den Vervollständigungshandler _createViewModels übergeben.

Hilo\Hilo\imageQueryBuilder.js


    if (this.settings.bindable) {
        // Create `Hilo.Picture` objects instead of returning `StorageFile` objects
        queryPromise = queryPromise.then(this._createViewModels);
    }


Sie können eine Zusage erstellen, ohne then direkt aufzurufen. Dazu können Sie einen Verweis auf die Zusage speichern und then zu einem späteren Zeitpunkt aufrufen. Ein Beispiel hierzu finden Sie unter Gruppieren einer Zusage.

Da der Aufruf von then selbst eine Zusage zurückgibt, können Sie mit then Zusagenketten erstellen und damit Ergebnisse an die einzelnen Zusagen in der Kette weitergeben. Der Rückgabewert kann ggf. ignoriert werden. Weitere Informationen zu Zusagen und anderen von Zusagen unterstützten Methoden finden Sie unter Asynchrone Programmierung in JavaScript.

[Oben]

So wird's gemacht: Verwenden einer Zusagenkette

Wir haben an mehreren Stellen in Hilo eine Zusagenkette erstellt, um eine Reihe von asynchronen Aufgaben zu unterstützen. Im folgenden Codebeispiel wird die TileUpdater.update-Methode veranschaulicht. Mit diesem Code wird der Vorgang zum Erstellen des Ordners für Miniaturbilder gesteuert, die Bilder werden ausgewählt, und die Kachel wird aktualisiert. Für einige dieser Aufgaben ist die Verwendung asynchroner Windows-Runtime-Funktionen (z. B. getThumbnailAsync) erforderlich. Andere Aufgaben in dieser Zusagenkette sind synchron. Wir wollten jedoch aus Konsistenzgründen alle Aufgaben als Zusagen darstellen.

Hilo\Hilo\Tiles\TileUpdater.js


update: function () {
    // Bind the function to a context, so that `this` will be resolved
    // when it is invoked in the promise.
    var queueTileUpdates = this.queueTileUpdates.bind(this);

    // Build a query to get the number of images needed for the tiles.
    var queryBuilder = new Hilo.ImageQueryBuilder();
    queryBuilder.count(numberOfImagesToRetrieve);

    // What follows is a chain of promises. These outline a number of 
    // asychronous operations that are executed in order. For more 
    // information on how promises work, see the readme.txt in the 
    // root of this project.
    var whenImagesForTileRetrieved = queryBuilder.build(picturesLibrary).execute();
    whenImagesForTileRetrieved
        .then(Hilo.Tiles.createTileFriendlyImages)
        .then(this.getLocalImagePaths)
        .then(Hilo.Tiles.createTileUpdates)
        .then(queueTileUpdates);
}


Jede Funktion in der Zusagenkette übergibt ihr Ergebnis als Eingabe an die nächste Funktion. Beispiel: Nach Abschluss von whenImagesForTileRetrieved wird der Vervollständigungshandler createTileFriendlyImages aufgerufen. Die Aufruffunktion übergibt dem Vervollständigungshandler automatisch ein Array mit den zurückgegebenen Dateien aus dem asynchronen Funktionsaufruf. In diesem Beispiel ist die asynchrone Funktion queryBuilder.build.execute, die eine Zusage zurückgibt.

Das folgende Diagramm veranschaulicht den Vorgangsflow in der Zusagenkette des Kachelupdaters. In diesem Flow werden Miniaturansichten und Kachelupdates erstellt. Das Erstellen einer Miniaturansicht aus einer Bilddatei involviert asynchrone Schritte, die kein geradliniges Muster befolgen. Die durchgehenden Ovale im Diagramm stellen asynchrone Windows-Runtime-Vorgänge dar. Bei den gestrichelten Ovalen handelt es sich um Aufgaben zum Aufrufen asynchroner Funktionen. Die Pfeile stehen für Ein- und Ausgaben.

Vorgangsflow in der Zusagenkette der Kachelaktualisierung

Weitere Informationen zu Zusageketten finden Sie unter Verketten von Zusagen.

[Oben]

Verwenden der bind-Funktion

Mit der JavaScript-bind-Funktion wird eine neue Funktion mit demselben Text wie die ursprüngliche Funktion erstellt. In der neuen Funktion wird das this-Schlüsselwort in den ersten an bind übergebenen Parameter aufgelöst. Sie können darüber hinaus im Aufruf von bind zusätzliche Parameter an die neue Funktion übergeben. Weitere Informationen finden Sie im Thema zur bind-Funktion.

Bei der Codierung asynchroner Vorgänge in Hilo war die Verwendung der Funktion bind in einigen Szenarien hilfreich. Im ersten Szenario haben wir bind verwendet, um die Auswertung von this im Ausführungskontext zu bewahren und lokale Variablen an den Abschluss weiterzugeben (typische Verwendung in JavaScript). Im zweiten Szenario wollten wir mehrere Parameter an den Vervollständigungshandler übergeben.

Der Code in TileUpdater.js ist ein Beispiel für das erste Szenario, in dem bind nützlich war. Der letzte Vervollständigungshandler in der Zusagenkette des Kachelupdaters, queueTileUpdates, wird an die update-Methode des Kachelupdaters gebunden (wie vorher dargestellt). Hier erstellen wir eine gebundene Version von queueTileUpdates mithilfe des this-Schlüsselworts. Zu diesem Zeitpunkt enthält this einen Verweis auf das TileUpdater-Objekt.

Hilo\Hilo\Tiles\TileUpdater.js


var queueTileUpdates = this.queueTileUpdates.bind(this);


Mithilfe von bind bewahren wir den Wert this für die Verwendung in der forEach-Schleife in queueTileUpdates. Ohne Bindung des TileUpdater-Objekts, das wir später einer lokalen Variablen (var self = this) zuordnen, würde bei Aufruf von this.tileUpdater.update(notification) in der forEach-Schleife eine Ausnahme für einen nicht definierten Wert auftreten.

Hilo\Hilo\Tiles\TileUpdater.js


queueTileUpdates: function (notifications) {
    var self = this;
    notifications.forEach(function (notification) {
        self.tileUpdater.update(notification);
    });
},


Das zweite Szenario, in dem bind zum Übergeben mehrerer Parameter hilfreich ist, wird in der Zusagenkette des Kachelupdaters gezeigt. Im ersten Vervollständigungshandler in der Zusagenkette des Kachelupdaters, createTileFriendlyImages, wird bind verwendet, um zwei Funktionen partiell anzuwenden. Weitere Informationen zur partiellen Anwendung von Funktionen finden Sie unter Partielle Anwendung und in folgendem Beitrag. In Hilo werden die Funktionen copyFilesToFolder und returnFileNamesFor als Parameter an den Kontext (in diesem Fall Null) und das Array der Dateien gebunden. Für copyFilesToFolder sollen zwei Argumenten übergeben werden: das Array mit den Dateien (Miniaturbilder) und das Ergebnis von createFolderAsync. Dies ist der Zielordner für die Miniaturansichten.

Hilo\Hilo\Tiles\createTileFriendlyImages.js


function createTileFriendlyImages(files) {
    var localFolder = applicationData.current.localFolder;

    // We utilize the concept of [Partial Application][1], specifically
    // using the [`bind`][2] method available on functions in JavaScript.
    // `bind` allows us to take an existing function and to create a new 
    // one with arguments that been already been supplied (or rather
    // "applied") ahead of time.
    //
    // [1]: http://en.wikipedia.org/wiki/Partial_application 
    // [2]: http://msdn.microsoft.com/en-us/library/windows/apps/ff841995

    // Partially apply `copyFilesToFolder` to carry the files parameter with it,
    // allowing it to be used as a promise/callback that only needs to have
    // the `targetFolder` parameter supplied.
    var copyThumbnailsToFolder = copyFilesToFolder.bind(null, files);

    // Promise to build the thumbnails and return the list of local file paths.
    var whenFolderCreated = localFolder.createFolderAsync(thumbnailFolderName, creationCollisionOption.replaceExisting);

    return whenFolderCreated
        .then(copyThumbnailsToFolder);
}


Tipp  Eine Bindung an Null ist möglich, da die gebundenen Funktionen nicht das this-Schlüsselwort nutzen.

Die gebundene Version von copyFilesToFolder wird copyThumbnailsToFolder zugewiesen. Beim Aufruf von copyFilesToFolder wird das zuvor gebundene Array mit Dateien als erster Eingabeparameter, sourceFiles, übergeben. Der im vorherigen Codebeispiel als Ergebnis von createFolderAsync gespeicherte Zielordner wird automatisch als zweiter Parameter (anstelle als einziger Parameter) in copyFilesToFolder übergeben.

Hilo\Hilo\Tiles\createTileFriendlyImages.js


function copyFilesToFolder(sourceFiles, targetFolder) {

    var allFilesCopied = sourceFiles.map(function (fileInfo, index) {
        // Create a new file in the target folder for each 
        // file in `sourceFiles`.
        var thumbnailFileName = index + ".jpg";
        var copyThumbnailToFile = writeThumbnailToFile.bind(this, fileInfo);
        var whenFileCreated = targetFolder.createFileAsync(thumbnailFileName, creationCollisionOption.replaceExisting);

        return whenFileCreated
            .then(copyThumbnailToFile)
            .then(function () { return thumbnailFileName; });
    });

    // We now want to wait until all of the files are finished 
    // copying. We can "join" the file copy promises into 
    // a single promise that is returned from this method.
    return WinJS.Promise.join(allFilesCopied);
};


[Oben]

Gruppieren einer Zusage

Bei nicht sequenziellen asynchronen Vorgängen, die erst abgeschlossen werden müssen, bevor Sie eine Aufgabe fortsetzen können, können Sie mithilfe der WinJS.Promise.join-Methode die Zusagen gruppieren. Das Ergebnis von Promise.join ist selbst eine Zusage. Diese Zusage wird erfolgreich abgeschlossen, wenn alle verknüpften Zusagen erfolgreich abgeschlossen wurden. Andernfalls wird die Zusage in einem Fehlerzustand zurückgegeben.

Im folgenden Code werden Kachelbilder in einen neuen Ordner kopiert. Wir warten dabei, bis wir einen neuen Ausgabestream (whenFileIsOpen) geöffnet und die Eingabedatei erhalten haben, die ein Kachelbild (whenThumbnailIsReady) enthält. Dann erst wird mit dem eigentlichen Kopieren des Bilds begonnen. Wir übergeben die zwei zurückgegebenen Zusagenobjekte an die join-Funktion.

Hilo\Hilo\Tiles\createTileFriendlyImages.js


var whenFileIsOpen = targetFile.openAsync(fileAccessMode.readWrite);
var whenThumbailIsReady = sourceFile.getThumbnailAsync(thumbnailMode.singleItem);
var whenEverythingIsReady = WinJS.Promise.join({ opened: whenFileIsOpen, ready: whenThumbailIsReady });


Bei der Verwendung von join werden die Rückgabewerte der verknüpften Zusagen als Eingabe an den Vervollständigungshandler übergeben. Wenn wir mit dem Kachelbeispiel fortfahren, enthält das unten aufgeführte args.opened-Element, den Rückgabewert von whenFileIsOpen, und args.ready enthält den Rückgabewert von whenThumbnailIsReady.

Hilo\Hilo\Tiles\createTileFriendlyImages.js


whenEverythingIsReady.then(function (args) {
    // `args` contains the output from both `whenFileIsOpen` and `whenThumbailIsReady`.
    // We can identify them by the order they were in when we joined them.
    outputStream = args.opened;
    var thumbnail = args.ready;


[Oben]

Schachtelung in einer Zusagenkette

Bei früheren Iterationen von Hilo haben wir Zusagenketten durch den Aufruf von then im Vervollständigungshandler für die vorhergehende Zusage erstellt. Dies führte zu tief geschachtelten und damit schwer lesbaren Ketten. Hier sehen Sie eine frühere Version der writeThumbnailToFile-Funktion.


   function writeThumbnailToFile(fileInfo, thumbnailFile) {   
       var whenFileIsOpen = thumbnailFile.openAsync(readWrite);   
       
       return whenFileIsOpen.then(function (outputStream) {   
       
           return fileInfo.getThumbnailAsync(thumbnailMode).then(function (thumbnail) {   
               var inputStream = thumbnail.getInputStreamAt(0);   
               return randomAccessStream.copyAsync(inputStream, outputStream).then(function () {   
                   return outputStream.flushAsync().then(function () {   
                       inputStream.close();   
                       outputStream.close();   
                       return fileInfo.name;   
                   });   
               });   
           });   
       });   
   }


Wir haben derartigen Code durch den direkten Aufruf von then für jede zurückgegebene Zusage statt im Vervollständigungshandler für die vorhergehende Zusage verbessert. Eine genauere Beschreibung finden Sie in diesem Beitrag. Die Semantik des folgenden Codes entspricht der des vorhergehenden Codes, abgesehen davon, dass wir auch join zum Gruppieren von Zusagen verwendet haben. Sie ist unserer Meinung nach jedoch einfacher zu lesen.

Hilo\Hilo\Tiles\createTileFriendlyImages.js


function writeThumbnailToFile(sourceFile, targetFile) {
    var whenFileIsOpen = targetFile.openAsync(fileAccessMode.readWrite);
    var whenThumbailIsReady = sourceFile.getThumbnailAsync(thumbnailMode.singleItem);
    var whenEverythingIsReady = WinJS.Promise.join({ opened: whenFileIsOpen, ready: whenThumbailIsReady });

    var inputStream,
        outputStream;

    whenEverythingIsReady.then(function (args) {
        // `args` contains the output from both `whenFileIsOpen` and `whenThumbailIsReady`.
        // We can identify them by the order they were in when we joined them.
        outputStream = args.opened;
        var thumbnail = args.ready;
        inputStream = thumbnail.getInputStreamAt(0);
        return randomAccessStream.copyAsync(inputStream, outputStream);

    }).then(function () {
        return outputStream.flushAsync();

    }).done(function () {
        inputStream.close();
        outputStream.close();
    });
}


Im vorhergehenden Code muss eine Fehlerbehandlungsfunktion als bewährte Methode aufgenommen werden, um sicherzustellen, dass die Eingabe- und Ausgabedatenströme geschlossen sind, wenn die Funktion zurückgegeben wird. Weitere Informationen finden Sie unter Behandeln von Fehlern.

Beachten Sie, dass wir in der ersten Implementierung einige Werte über den Abschluss (z. B. inputStream und outputStream) weitergegeben haben. In der zweiten Implementierung mussten die Werte im äußeren Bereich deklariert werden, da er den einzigen gemeinsamen Abschluss darstellte.

[Oben]

Umbrechen von Werten in einer Zusage

Wenn Sie eigene Objekte zum Ausführen asynchroner Aufrufe erstellen, müssen Sie unter Umständen mit Promise.as Werte, die keine Zusage darstellen, explizit in einer Zusage umbrechen. Im Abfrage-Generator brechen wir beispielsweise den Rückgabewert von Hilo.Picture.from in eine Zusage um, da von der from-Funktion letztlich verschiedene andere asynchrone Windows-Runtime-Methoden (getThumbnailAsync und retrievePropertiesAsync) aufgerufen werden.

Hilo\Hilo\imageQueryBuilder.js


_createViewModels: function (files) {
    return WinJS.Promise.as(files.map(Hilo.Picture.from));
}


Weitere Informationen zu Hilo.Picture-Objekten finden Sie unter Verwenden des Abfrage-Generator-Musters.

[Oben]

Behandeln von Fehlern

Tritt im Vervollständigungshandler ein Fehler auf, gibt die then-Funktion eine Zusage im Fehlerzustand zurück. Enthält die Zusagenkette keinen Fehlerhandler, wird der Fehler vielleicht nicht angezeigt. Um diese Situation zu vermeiden, wird empfohlen, einen Fehlerhandler in die letzte Klausel der Zusagenkette aufzunehmen. Der Fehlerhandler ermittelt Fehler, die bei der Ausführung auftreten.

Wichtig  Es wird empfohlen, am Ende einer Zusagenkette done anstelle von then zu verwenden. Die Syntax der beiden Elemente ist gleich. Mit then kann die Kette jedoch fortgesetzt werden. done gibt keine weitere Zusage zurück, daher kann die Kette nicht fortgesetzt werden. Verwenden Sie done anstelle von then am Ende einer Zusagenkette, um sicherzustellen, dass unbehandelte Ausnahmen ausgelöst werden. Weitere Informationen finden Sie unter So wird's gemacht: Behandeln von Fehlern mit Zusagen.

Im folgenden Codebeispiel wird die Verwendung von done in einer Zusagenkette veranschaulicht.

Hilo\Hilo\month\monthPresenter.js


return this._getMonthFoldersFor(targetFolder)
    .then(this._queryImagesPerMonth)
    .then(this._buildViewModelsForMonths)
    .then(this._createDataSources)
    .then(function (dataSources) {
        self._setupListViews(dataSources.images, dataSources.years);
        self.loadingIndicatorEl.style.display = "none";
        self.selectLayout();
    });


In Hilo implementieren wir eine Fehlerbehandlungsfunktion bei dem Versuch, ein zugeschnittenes Bild auf der Seite für den Bildzuschnitt zu speichern. Die Fehlerbehandlungsfunktion muss als zweiter Parameter an then oder done übergeben werden. In diesem Code korrigiert die Fehlerbehandlungsfunktion die Fotoausrichtung, falls eine EXIF-Ausrichtung zurückgegeben, aber nicht unterstützt wird.

Hilo\Hilo\crop\imageView.js


var decoderPromise = getOrientation
    .then(function (retrievedProps) {

        // Even though the EXIF properties were returned, 
        // they still might not include the `System.Photo.Orientation`.
        // In that case, we will assume that the image is not rotated.
        exifOrientation = (retrievedProps.size !== 0)
            ? retrievedProps["System.Photo.Orientation"]
            : photoOrientation.normal;

    }, function (error) {
        // The file format does not support EXIF properties, continue 
        // without applying EXIF orientation.
        switch (error.number) {
            case Hilo.ImageWriter.WINCODEC_ERR_UNSUPPORTEDOPERATION:
            case Hilo.ImageWriter.WINCODEC_ERR_PROPERTYNOTSUPPORTED:
                // The image does not support EXIF orientation, so
                // set it to normal. this allows the getRotatedBounds
                // to work propertly.
                exifOrientation = photoOrientation.normal;
                break;
            default:
                throw error;
        }
    });


Informationen zu bewährten Methoden für Komponententests und Fehlerbehandlung bei asynchronen Aufrufen finden Sie unter Testen und Bereitstellen der App.

[Oben]

 

 

Anzeigen:
© 2015 Microsoft