Optimisation du rendu des éléments du contrôle ListView

Blog des développeurs d'applications Windows 8

Indications sur la conception d'applications de style Metro pour Windows 8, par l'équipe d'ingénierie de Windows 8

Optimisation du rendu des éléments du contrôle ListView

  • Comments 0

Pour de nombreuses applications du Windows Store écrites en JavaScript qui utilisent les collections, une coopération appropriée avec le contrôle ListView WinJS est essentielle pour optimiser leurs performances. Ceci n'est pas surprenant : lorsque vous devez gérer et afficher des milliers d'éléments potentiels, toutes les optimisations que vous réalisez sur ces éléments font la différence. Ce qui compte le plus, c'est la manière dont chacun de ces éléments est rendu, c'est-à-dire comment (et quand) chaque élément du contrôle ListView est élaboré dans le DOM et apparaît dans l'application. En effet, la composante quand de cette équation devient un facteur critique lorsqu'un utilisateur se déplace rapidement dans une liste et s'attend à ce que cette liste soutienne le rythme qu'il lui impose.

Le rendu des éléments dans un contrôle ListView se produit via un modèle déclaratif défini en HTML ou via une fonction de rendu JavaScript personnalisée qui est appelée pour chaque élément de la liste. Même si l'utilisation d'un modèle déclaratif est la méthode la plus simple, elle ne confère pas une latitude très importante pour contrôler précisément le processus. Une fonction de rendu, d'un autre côté, vous permet de personnaliser le rendu élément par élément et de mettre en œuvre différentes optimisations qui sont illustrées dans l'exemple d'optimisation des performances de contrôle HTML ListView. Ces optimisations sont les suivantes :

  • Autoriser la distribution asynchrone des données des éléments et d'un élément rendu. Ceci est pris en charge par les fonctions de rendu élémentaires.
  • Séparer la création de la forme d'un élément, qui est nécessaire à la disposition globale du contrôle ListView, de ses éléments internes. Ceci est pris en charge via un convertisseur d'espace réservé.
  • Réutiliser un élément créé au préalable (et ses enfants) en remplaçant ses données, ce qui évite la plupart des étapes de création d'un élément. Ceci est possible via un convertisseur d'espace réservé de recyclage.
  • Reporter les opérations visuelles intensives, telles que le chargement d'images et les animations, jusqu'à ce qu'un élément soit visible et tant que le contrôle ListView n'est pas déplacé rapidement. Ceci s'effectue via un convertisseur à plusieurs phases.
  • Traiter par lot les opérations visuelles identiques afin de limiter la répétition des rendus du DOM avec un convertisseur par lot à plusieurs phases.

Dans ce billet, nous allons examiner toutes ces étapes et voir comment elles coopèrent avec le processus de rendu des éléments du contrôle ListView. Comme vous pouvez l'imaginer, les optimisations qui entourent le moment où les éléments sont rendus entraînent un grand nombre d'opérations asynchrones et véhiculent par conséquent beaucoup de promesses. Au cours du processus, nous allons également acquérir une connaissance plus approfondie des promesses, en nous appuyant sur le billet précédent Tout sur les promesses de ce blog.

De manière générale pour tous les convertisseurs, il est important de toujours ramener au minimum le temps de rendu des principaux éléments (sans compter les opérations reportées). Comme les performances finales du contrôle ListView dépendent de beaucoup du bon alignement de ses mises à jour sur les intervalles d'actualisation de l'écran, quelques millisecondes supplémentaires dans un convertisseur d'élément peut faire dépasser le temps de rendu global du contrôle ListView sur le prochain intervalle d'actualisation, entraînant la suppression d'images et des irrégularités visuelles. En d'autres termes, il est réellement très important d'optimiser votre code JavaScript dans les convertisseurs d'éléments.

Convertisseurs élémentaires

Commençons par passer rapidement en revue la fonction de rendu des éléments (que je vais simplement appeler convertisseur. Un convertisseur est une fonction que vous attribuez à la propriété itemTemplate du contrôle ListView à la place d'un nom de modèle. Cette fonction est appelée, lorsque cela est nécessaire, pour les éléments que le contrôle ListView souhaite inclure dans le DOM. (Vous trouverez une documentation élémentaire sur les convertisseurs à la page itemTemplate, mais l'exemple illustre mieux les optimisations.)

Vous pouvez penser qu'une fonction de rendu des éléments reçoit simplement un élément de la source de données du contrôle ListView. Qu'elle crée ensuite les composants HTML nécessaires à cet élément en particulier et renvoie le composant racine que le contrôle ListView peut ajouter au DOM. C'est essentiellement ce qui se passe, mais vous devez prendre en considération deux points complémentaires. D'abord, comme les données de l'élément même peuvent être chargées de manière asynchrone, il est logique de lier la création des composants à la disponibilité de ces données. Ensuite, le processus de rendu de l'élément même peut entraîner d'autres tâches asynchrones, telles que le chargement d'images à partir d'URI à distance ou la lecture de données dans d'autres fichiers identifiés dans les données de l'élément. Les différents niveaux d'optimisation que nous allons voir, en fait, peuvent donner lieu à un nombre arbitraire de tâches asynchrones entre la demande des composants de l'élément et la distribution effective de ces composants.

Ici encore, vous pouvez donc vous attendre à voir apparaître des promesses ! D'abord, le contrôle ListView ne communique pas simplement les données de l'élément au convertisseur directement ; il fournit une promesse concernant ces données. Et la fonction ne renvoie pas directement le composant racine de l'élément ; elle renvoie une promesse concernant ce composant. Ceci permet au contrôle ListView d'associer plusieurs promesses de rendu d'éléments et d'attendre (en mode asynchrone) qu'une page entière d'éléments soit rendue. En fait, il procède ainsi pour gérer intelligemment l'élaboration de différentes pages, en créant d'abord la page des éléments visibles, puis deux pages hors écran avant et après (les deux pages que les utilisateurs ont le plus de chances de consulter ensuite). En outre, la mise en place de toutes ces promesses signifie que le contrôle ListView peut facilement annuler le rendu des éléments non terminés si l'utilisateur s'éloigne, ce qui évite de créer inutilement des composants.

Nous pouvons voir comment ces promesses sont utilisées dans la fonction simpleRenderer de l'exemple :

 

function simpleRenderer(itemPromise) {
return itemPromise.then(function (item) {
var element = document.createElement("div");
element.className = "itemTempl";
element.innerHTML = "<img src='" + item.data.thumbnail +
"' alt='Databound image' /><div class='content'>" + item.data.title + "</div>";
return element;
});
}

Ce code associe d'abord un gestionnaire Terminé à itemPromise. Le gestionnaire est appelé lorsque les données de l'élément sont disponibles et répond en créant les composants. Mais remarquez à nouveau que nous ne renvoyons pas l'élément directement : nous renvoyons une promesse qui est réalisée avec ce composant. En d'autres termes, la valeur renvoyée par itemPromise.then() est une promesse qui est réalisée avec element si et quand le contrôle ListView en a besoin.

Le renvoi d'une promesse nous permet d'effectuer d'autres tâches de synchronisation si nécessaire. Dans ce cas, le convertisseur peut simplement relier ces promesses intermédiaires et renvoyer la promesse issue du dernier appel de la méthode then dans la chaîne. Par exemple :

function someRenderer(itemPromise) {
return itemPromise.then(function (item) {
return doSomeWorkAsync(item.data);
}).then(function (results) {
return doMoreWorkAsync(results1);
}).then(function (results2) {
var element = document.createElement("div");
// use results2 to configure the element
return element;
});
}

Notez que dans ce cas, nous n'utilisons pas done à la fin de la chaîne, car nous renvoyons la promesse issue du dernier appel de la méthode then. Le contrôle ListView est chargé de la gestion des erreurs susceptibles de survenir.

Convertisseurs d'espaces réservés

La prochaine étape d'optimisation ListView utilise un convertisseur d'espace réservé qui décompose la création du composant en deux phases. Cela permet au contrôle ListView de demander uniquement les parties d'un composant qui sont nécessaires pour définir la disposition globale de la liste, sans créer tous les composants à l'intérieur de chaque élément. Le contrôle ListView peut ainsi s'acquitter de sa passe de disposition rapidement tout en demeurant hautement réactif aux saisies. Il peut ensuite demander le reste du composant plus tard.

Un convertisseur d'espace réservé renvoie un objet avec deux propriétés au lieu d'une simple promesse :

  • element Composant de niveau supérieur dans la structure de l'élément qui permet de définir sa taille et sa forme et ne dépend pas des données de l'élément.
  • renderComplete Promesse qui est réalisée lorsque le reste du contenu du composant est créé. En d'autres termes, cette propriété renvoie la promesse à partir de la chaîne que vous avez démarrée avec itemPromise.then comme dans l'exemple précédent.

Le contrôle ListView est suffisamment intelligent pour savoir si votre convertisseur renvoie une promesse (cas élémentaire de l'exemple précédent) ou un objet avec les propriétés element et renderComplete (cas plus avancés). Ainsi, le convertisseur d'espace réservé équivalent (dans l'exemple) pour la fonction simpleRenderer précédente ressemble à ceci :

function placeholderRenderer(itemPromise) {
// create a basic template for the item that doesn't depend on the data
var element = document.createElement("div");
element.className = "itemTempl";
element.innerHTML = "<div class='content'>...</div>";

// return the element as the placeholder, and a callback to update it when data is available
return {
element: element,

// specifies a promise that will be completed when rendering is complete
// itemPromise will complete when the data is available
renderComplete: itemPromise.then(function (item) {
// mutate the element to include the data
element.querySelector(".content").innerText = item.data.title;
element.insertAdjacentHTML("afterBegin", "<img src='" +
item.data.thumbnail + "' alt='Databound image' />");
})
};
}

Notez que l'affectation element.innerHTML peut être déplacée à l'intérieur de renderComplete, car la classe itemTempl dans le fichier css/scenario1.css de l'exemple stipule directement la largeur et la hauteur de l'élément. L'affectation est incluse dans la propriété element, car elle fournit le texte « … » par défaut dans l'espace réservé. Il serait également possible d'utiliser un composant img se rapportant à une petite ressource dans le package qui est partagée dans tous les éléments (et dont le rendu est par conséquent rapide).

Convertisseurs d'espaces réservés de recyclage

La prochaine optimisation, le convertisseur d'espace réservé de recyclage, n'apporte aucune nouveauté sur le front des promesses. Elle met en revanche l'accent sur un deuxième paramètre du convertisseur, nommé recycled. Il s'agit du composant racine d'un élément qui a déjà été rendu mais qui n'est plus visible. Ainsi, les composants enfants du composant « recycled » (recyclé) sont déjà en place et vous pouvez simplement remplacer les données et même transformer certains de ces composants. Ceci évite la plupart des coûteux appels de création de composants qui sont nécessaires pour créer un élément entièrement nouveau et permet d'accélérer le processus de rendu.

Le contrôle ListView peut fournir un composant « recycled » lorsque sa propriété loadingBehavior est définie sur « randomaccess ». Si le composant recycled est stipulé, vous pouvez simplement supprimer les données du composant (et ses enfants), le renvoyer comme espace réservé, puis indiquer d'autres données et créer des enfants supplémentaires (le cas échéant) dans renderComplete. Si aucun composant « recycled » n'est fourni (par exemple si le contrôle ListView est créé en premier ou si loadingBehavior est « incrémentiel »), vous devez créer entièrement le composant. Voici le code de l'exemple correspondant à cette variante :

function recyclingPlaceholderRenderer(itemPromise, recycled) {
var element, img, label;
if (!recycled) {
// create a basic template for the item that doesn't depend on the data
element = document.createElement("div");
element.className = "itemTempl";
element.innerHTML = "<img alt='Databound image' style='visibility:hidden;'/>" +
"<div class='content'>...</div>";
}
else {
// clean up the recycled element so that we can reuse it
element = recycled;
label = element.querySelector(".content");
label.innerHTML = "...";
img = element.querySelector("img");
img.style.visibility = "hidden";
}
return {
element: element,
renderComplete: itemPromise.then(function (item) {
// mutate the element to include the data
if (!label) {
label = element.querySelector(".content");
img = element.querySelector("img");
}
label.innerText = item.data.title;
img.src = item.data.thumbnail;
img.style.visibility = "visible";
})
};
}

Dans renderComplete, veillez à vérifier l'existence des composants que vous ne créez pas pour un nouvel espace réservé (par exemple label) et créez-les si cela est nécessaire.

Si vous souhaitez supprimer les éléments recyclés de manière plus générale, notez que vous pouvez attribuer une fonction à la propriété resetItem du contrôle ListView. Le code de cette fonction serait alors similaire à celui présenté ci-dessus. Il en va de même pour la propriété resetGroupHeader, car vous pouvez utiliser les fonctions de modèles pour les en-têtes de groupes comme pour les éléments. Nous en avons peu parlé, car les en-têtes de groupes sont beaucoup moins nombreux et les implications en termes de performances ne sont généralement pas les mêmes. La fonctionnalité existe néanmoins.

Convertisseurs à plusieurs phases

Tout ceci nous amène à l'avant-dernière optimisation, le convertisseur à plusieurs phases. Il prolonge le convertisseur d'espace réservé de recyclage aux images et autres données multimédias à chargement différé tant que le reste de l'élément n'est pas entièrement présent dans le DOM. Il retarde également des effets, tels que les animations, tant que l'élément n'est pas réellement à l'écran. Les utilisateurs se déplaçant souvent très rapidement au sein du contrôle ListView, il est logique de reporter de manière asynchrone les opérations les plus exigeantes jusqu'à ce que le contrôle parvienne à une position stable.

Le contrôle ListView offre les points de raccordement nécessaires lorsque les membres de item sont issus de itemPromise : une propriété nommée ready (une promesse) et deux méthodes, loadImage et isOnScreen, toutes les deux renvoyant plus de promesses. C'est-à-dire :

renderComplete: itemPromise.then(function (item) {
// item.ready, item.loadImage, and item.isOnScreen available
})

Voici comment les utiliser :

  • ready Renvoie cette promesse à partir du premier gestionnaire Terminé de votre chaîne. Cette promesse est réalisée lorsque l'intégralité de la structure du composant a été rendue et est visible. Cela signifie que vous pouvez associer une autre méthode then à un gestionnaire Terminé dans lequel vous effectuez d'autres tâches postérieures à la visibilité, par exemple le chargement d'images.
  • loadImage Télécharge une image à partir d'un URI et l'affiche dans le composant img donné, ce qui renvoie une promesse qui est réalisée avec ce même composant. Vous joignez un gestionnaire Terminé à cette promesse, qui renvoie à son tour la promesse depuis isOnScreen. Notez que loadImage crée un composant img si aucun n'est fourni et le distribue à votre gestionnaire Terminé.
  • isOnScreen Renvoie une promesse dont la valeur de réalisation est une valeur booléenne qui indique si le composant est visible ou non. Dans les implémentations actuelles, il s'agit d'une valeur connue et la promesse est donc réalisée de manière synchrone. En l'incluant dans une promesse, cependant, elle peut être utilisée dans une chaîne plus longue.

Tout cela est illustré dans la fonction multistageRenderer de l'exemple, où la fin du chargement de l'image déclenche l'apparition en fondu d'une animation. Voici simplement ce qui est renvoyé par la promesse renderComplete :

renderComplete: itemPromise.then(function (item) {
// mutate the element to update only the title
if (!label) { label = element.querySelector(".content"); }
label.innerText = item.data.title;

// use the item.ready promise to delay the more expensive work
return item.ready;
// use the ability to chain promises, to enable work to be cancelled
}).then(function (item) {
// use the image loader to queue the loading of the image
if (!img) { img = element.querySelector("img"); }
return item.loadImage(item.data.thumbnail, img).then(function () {
// once loaded check if the item is visible
return item.isOnScreen();
});
}).then(function (onscreen) {
if (!onscreen) {
// if the item is not visible, don't animate its opacity
img.style.opacity = 1;
} else {
// if the item is visible, animate the opacity of the image
WinJS.UI.Animation.fadeIn(img);
}
})

Même si un grand nombre d'opérations se déroulent, cette chaîne de promesses demeure élémentaire. La première opération asynchrone du convertisseur actualise les parties simples de la structure des composants de l'élément, par exemple le texte. Elle renvoie ensuite la promesse dans item.ready. Une fois cette promesse réalisée (ou, plus précisément, si cette promesse est réalisée), nous utilisons la méthode asynchrone loadImage de l'élément pour déclencher le téléchargement d'une image, ce qui renvoie la promesse item.isOnScreen depuis ce gestionnaire Terminé. Cela signifie que l'indicateur de visibilité onscreen est transmis au gestionnaire Terminé final de la chaîne. Lorsque (et si) cette promesse isOnScreen est réalisée (ce qui signifie que l'élément est réellement visible), nous pouvons effectuer les opérations appropriées, telles que des animations.

Je tiens à souligner le caractère hypothétique (avec le terme « si »), car il est à nouveau probable que l'utilisateur se déplace au sein du contrôle ListView pendant le déroulement de toutes ces opérations. L'enchaînement de toutes ces promesses permet au contrôle ListView d'annuler les opérations asynchrones lorsque ces éléments sont déplacés hors de l'écran et/ou hors des pages mises en mémoire tampon. Qu'il suffise de dire que le contrôle ListView a subi à un grand nombre de tests de performances !

Il est également important de se rappeler que nous utilisons la méthode then dans toutes ces chaînes, car nous renvoyons toujours une promesse depuis la fonction de rendu au sein de la propriété renderComplete. Nous ne sommes jamais à la fin de la chaîne dans ces convertisseurs et nous n'utiliserons donc jamais done à la fin.

Traitement de miniatures par lot

La dernière optimisation constitue sans aucun doute le coup de grâce pour le contrôle ListView. Dans la fonction nommée batchRenderer, nous trouvons cette structure pour renderComplete (la plupart du code est omis) :

renderComplete: itemPromise.then(function (item) {
// mutate the element to update only the title
if (!label) { label = element.querySelector(".content"); }
label.innerText = item.data.title;

// use the item.ready promise to delay the more expensive work
return item.ready;
// use the ability to chain promises, to enable work to be cancelled
}).then(function (item) {
// use the image loader to queue the loading of the image
if (!img) { img = element.querySelector("img"); }
return item.loadImage(item.data.thumbnail, img).then(function () {
// once loaded check if the item is visible
return item.isOnScreen();
});
}).then(function (onscreen) {
if (!onscreen) {
// if the item is not visible, don't animate its opacity
img.style.opacity = 1;
} else {
// if the item is visible, animate the opacity of the image
WinJS.UI.Animation.fadeIn(img);
}
})

Cela ressemble beaucoup à la fonction multistageRenderer, à l'exception de l'insertion de cet appel mystérieux destiné à la fonction thumbnailBatch entre l'appel item.loadImage et la vérification item.isOnScreen. La position de thumbnailBatch dans la chaîne indique que la valeur renvoyée doit être un gestionnaire Terminé qui renvoie lui-même une autre promesse.

Ce n'est pas très clair ? Nous y reviendrons en détail ! Mais d'abord, nous avons besoin d'informations complémentaires sur ce que nous tentons d'accomplir.

Si nous n'avions qu'un contrôle ListView avec un seul élément, les différentes optimisations de chargement ne seraient pas significatives. Mais le contrôle ListView comporte généralement de nombreux éléments et la fonction de rendu est appelée pour chacun d'entre eux. Dans la fonction multistageRenderer de la section précédente, le rendu de chaque élément déclenche une opération item.loadImage asynchrone qui télécharge sa miniature à partir d'un URI arbitraire. La durée de chaque opération peut être arbitraire. Ainsi, pour toute la liste, plusieurs appels loadImage peuvent se dérouler simultanément, avec le rendu de chaque élément attendant la fin de sa miniature en particulier. Jusque là, tout va bien.

Une des caractéristiques importantes qui n'est pas du tout visible dans la fonction multistageRenderer, cependant, est que le composant img de la miniature est déjà présent dans le DOM et que la fonction loadImage définit l'attribut src de cette image dès la fin du téléchargement. Ceci déclenche à son tour une mise à jour dans le moteur de rendu dès que nous revenons du reste de la chaîne de promesses, qui est essentiellement synchrone au-delà de ce point.

Il est alors possible que certaines des miniatures réintègrent rapidement le thread d'interface utilisateur. Ceci entraînera une trop grande évolution dans le moteur de rendu et des performances visuelles médiocres. Pour éviter cette situation, nous voulons créer entièrement ces composants img avant qu'ils ne soient dans le DOM, puis les ajouter par lots afin qu''ils soient tous gérés au cours d'une passe de rendu unique.

L'exemple effectue ces opérations à l'aide de la fonction createBatch. La fonction createBatch est appelée une seule fois pour toute l'application et son résultat (une autre fonction) est stocké dans la variable nommée thumbnailBatch:

var thumbnailBatch;
thumbnailBatch = createBatch();

Un appel à cette fonction thumbnailBatch, comme je vais désormais la désigner, est à nouveau inséré dans la chaîne de promesses du convertisseur. L'objectif de cette insertion, étant donné la nature du code de traitement par lot que nous examinerons plus loin, est de regrouper un ensemble de composants img chargés, en les libérant pour d'autres traitements à intervalles appropriés. Ici encore, en regardant simplement la chaîne de promesses dans le convertisseur, un appel à thumbnailBatch() doit renvoyer une fonction de gestionnaire Terminé qui renvoie une promesse et la valeur de réalisation de cette promesse (en regardant la prochaine étape de la chaîne) doit être un composant img qui peut ensuite être ajouté au DOM. En ajoutant ces images au DOM après le traitement par lot, nous associons l'intégralité du groupe dans la même passe de rendu.

Cette différence entre la fonction batchRenderer et la fonction multistageRenderer de la section précédente est importante : dans la deuxième fonction, le composant img de la miniature existe déjà dans le DOM et est transmis à la méthode loadImage comme deuxième paramètre. Ainsi, lorsque la méthode loadImage définit l'attribut src de l'image, le rendu est mis à jour. Au sein de la fonction batchRenderer toutefois, ce composant img est créé séparément dans la méthode loadImage (où l'attribut src est également défini), mais le composant img n'est pas encore présent dans le DOM. Il n'est ajouté au DOM qu'à la fin de l'étape thumbnailBatch, ce qui lui permet de faire partie d'un groupe au sein de cette passe de disposition unique.

Examinons maintenant le fonctionnement de ce traitement par lot. Voici la fonction createBatch dans son intégralité :

function createBatch(waitPeriod) {
var batchTimeout = WinJS.Promise.as();
var batchedItems = [];

function completeBatch() {
var callbacks = batchedItems;
batchedItems = [];
for (var i = 0; i < callbacks.length; i++) {
callbacks[i]();
}
}

return function () {
batchTimeout.cancel();
batchTimeout = WinJS.Promise.timeout(waitPeriod || 64).then(completeBatch);

var delayedPromise = new WinJS.Promise(function (c) {
batchedItems.push(c);
});

return function (v) {
return delayedPromise.then(function () {
return v;
});
};
};
}

Ici encore, la fonction createBatch est appelée une seule fois et son résultat thumbnailBatch est appelé pour chaque élément rendu dans la liste. Le gestionnaire Terminé que thumbnailBatch génère est ensuite appelé dès qu'une opération loadImage se termine.

Ce gestionnaire Terminé aurait pu être inséré directement dans la fonction de rendu, mais nous essayons dans ce cas de coordonner les activités sur plusieurs éléments à la fois, et non élément par élément. Cette coordination est possible grâce aux deux variables créées et initialisées au début de la fonction createBatch : batchedTimeout, initialisée en tant que promesse vide, et batchedItems, initialisée en tant que tableau de fonctions vide à l'origine. createBatch déclare également une fonction, completeBatch, qui vide simplement batchedItems, en appelant chaque fonction du tableau :

function createBatch(waitPeriod) {
var batchTimeout = WinJS.Promise.as();
var batchedItems = [];

function completeBatch() {
var callbacks = batchedItems;
batchedItems = [];
for (var i = 0; i < callbacks.length; i++) {
callbacks[i]();
}
}

return function () {
batchTimeout.cancel();
batchTimeout = WinJS.Promise.timeout(waitPeriod || 64).then(completeBatch);

var delayedPromise = new WinJS.Promise(function (c) {
batchedItems.push(c);
});

return function (v) {
return delayedPromise.then(function () {
return v;
});
};
};
}

Étudions maintenant ce qui se passe dans thumbnailBatch (la fonction renvoyée par createBatch), qui est à nouveau appelée pour chaque élément rendu. D'abord, nous annulons les variables batchedTimeout existantes et en recréons une immédiatement :

batchTimeout.cancel();
batchTimeout = WinJS.Promise.timeout(waitPeriod || 64).then(completeBatch);

La deuxième ligne illustre le futur modèle de distribution/réalisation abordé dans le billet Tout sur les promesses <TODO: link>: elle demande d'appeler completeBatch après un délai de waitPeriod millisecondes (avec une valeur par défaut de 64 ms). Cela signifie que tant que la fonction thumbnailBatch est rappelée dans un intervalle de waitPeriod par rapport à un appel précédent, la variable batchTimeout est réinitialisée sur un autre délai waitPeriod. Et comme la fonction thumbnailBatch est appelée uniquement après la fin d'un appel item.loadImage, cela revient à dire que les opérations loadImage qui se terminent dans l'intervalle waitPeriod par rapport à la précédente seront incluses dans le même lot. Lorsque l'intervalle est supérieur à waitPeriod, le lot est traité (les images sont ajoutées au DOM) et le prochain lot commence.

Une fois ces délais d'expiration gérés, la fonction thumbnailBatch crée une nouvelle promesse qui charge simplement la fonction de répartiteur complète dans le tableau batchedItems :

var delayedPromise = new WinJS.Promise(function (c) {
batchedItems.push(c);
});

Comme l'indique le billet Tout sur les promesses <TODO: link> n'oubliez pas qu'une promesse n'est ni plus ni moins qu'une construction de code, et c'est tout ce que nous avons ici. La promesse qui vient d'être créée n'est associée à aucun comportement asynchrone : nous ajoutons simplement la fonction de répartiteur complète c dans batchedItems. Il est évident toutefois que nous ne touchons pas au répartiteur tant que la fonction batchedTimeout n'est pas terminée de manière asynchrone. Il existe par conséquent une relation asynchrone dans ce cas. Lorsque le délai d'expiration se produit et que nous effaçons le lot (à l'intérieur de completeBatch), nous appelons les gestionnaires Terminé qui sont indiqués ailleurs dans delayedPromise.then.

Ceci nous amène aux dernières lignes de code dans createBatch, qui est la fonction que thumbnailBatch renvoie réellement. Cette fonction est exactement le gestionnaire Terminé qui est inséré dans l'intégralité de la chaîne de promesses du convertisseur.

return function (v) {
return delayedPromise.then(function () {
return v;
});
};

En fait, insérons ce code directement dans la chaîne de promesses pour voir les relations que nous obtenons :

return item.loadImage(item.data.thumbnail);
}).then(function (v) {
return delayedPromise.then(function () {
return v;
});
).then(function (newimg) {

Nous voyons maintenant que l'argument v est le résultat de item.loadImage, qui est le composant img créé à notre intention. Si nous ne voulions pas du traitement par lot, nous pourrions simplement indiquer return WinJS.Promise.as(v) et toute la chaîne fonctionnerait parfaitement : v serait dans ce cas transmis de manière synchrone et apparaîtrait comme newimg à la prochaine étape.

Nous renvoyons toutefois à la place une promesse depuis delayedPromise.then qui ne sera pas réalisée (avec v) tant que la fonction batchedTimeout n'est pas réalisée. À ce stade (lorsqu'un délai de waitPeriod sépare la fin des opérations loadImage), ces composants img sont ensuite distribués à la prochaine étape de la chaîne, où ils sont ajoutés au DOM.

Et c'est tout !

Pour conclure

Les cinq différentes fonctions de rendu illustrées dans l'exemple d'optimisation des performances de contrôle HTML ListView ont toutes une caractéristique en commun : elles montrent comment la relation asynchrone entre le contrôle ListView et le convertisseur (exprimée au moyen de promesses) confère au convertisseur une extrême flexibilité quant à la manière dont il produit des composants pour les éléments de la liste et quant au moment où il les produit. Pour écrire vos propres applications, la stratégie que vous utilisez pour l'optimisation du contrôle ListView dépend énormément de la taille de votre source de données, de la complexité des éléments eux-mêmes et de la quantité de données que vous obtenez de manière asynchrone pour ces éléments (téléchargement d'images distantes, par exemple). Pour de nombreuses raisons, il est souhaitable de simplifier au maximum les convertisseurs d'éléments en vue de satisfaire vos objectifs de performances. Mais dans tous les cas, vous disposez maintenant de tous les outils dont vous avez besoin pour optimiser les performances du contrôle ListView et de votre application.

Kraig Brockschmidt
Chef de projet, équipe Écosystème Windows
Auteur de Programming Windows 8 Apps in HTML, CSS, and JavaScript

  • Loading...
Leave a Comment
  • Please add 4 and 6 and type the answer here:
  • Post