Otimizar a renderização de item do ListView
Para muitos aplicativos da Windows Store escritos em JavaScript , que trabalham com coleções, colaborar bem com o controle WinJS ListView é essencial para o bom desempenho do aplicativo. Isto não é nenhuma surpresa: quando se está lidando com o gerenciamento e a exibição de, possivelmente, milhares de itens, qualquer otimização que você fizer com esses itens conta. Ainda mais importante é como cada um desses itens é renderizado, o que significa, como — e quando — cada item no controle ListView é construído no DOM e se torna uma parte visível do aplicativo. Na verdade, a parte quando dessa equação se torna um fator crucial quando um usuário realiza uma panorâmica com rapidez em uma lista e espera que aquela lista mantenha o ritmo.
A renderização de itens em um ListView acontece por meio de um modelo declarativo, definido em HTML ou por meio de uma função de renderização personalizada do JavaScript que é exigida para cada item na lista. Embora o modelo declarativo seja o método mais simples, ele não oferece muita margem para o controle específico sobre o processo. Por outro lado, uma função de renderização o permite personalizar a renderização por cada item e possibilita diversas otimizações demonstradas na amostra de desempenho de otimização do ListView em HTML. Essas otimizações são:
- A possibilidade de entregas assíncronas de dados dos itens e um elemento renderizado, com o suporte das funções básicas de renderização.
- Separação da criação do formato de um item dos seus elementos internos, o que é necessário para o layout geral de um ListView. Este processo é possível por meio de um renderizador de espaço reservado.
- Reutilização de um elemento de item previamente criado (e dos seus filhos) substituindo os seus dados, evitando a maioria das etapas de criação de elemento, possibilitado por meio de um renderizador de reciclagem de espaço reservado.
- Adiar operações visuais caras, como carregamento e animação de imagens, até que um item se torne visível e um ListView não esteja sendo pesquisado panoramicamente, o que é realizado por meio de um renderizador de várias etapas.
- Agrupar essas mesmas operações visuais para minimizar a novas renderizações do DOM, por meio de um renderizador de agrupamento de várias etapas.
Nesta postagem analisaremos todas essas etapas e aprenderemos como elas cooperam com o processo de renderização de item do ListView. Como você pode imaginar, otimizações relacionadas a quando renderizar um item envolvem muitas operações assíncronas e, portanto, muitas promessas. Portanto, durante o processo, também aprenderemos muito sobre as próprias promessas, adicionando ao artigo anterior All about promises (Tudo sobre promessas) neste blog.
Uma observação geral, que se aplica a todos os renderizadores: é sempre importante manter o tempo de renderização do seu item principal (sem contar as operações adiadas) ao mínimo. Como o desempenho final do ListView depende muito de como as suas atualizações estão alinhadas com os intervalos de atualização da tela, uns poucos milissegundos gastos em um renderizador de item pode aumentar o tempo geral de renderização do ListView no curso do intervalo de atualização seguinte, resultando em quadros eliminados e falhas na imagem. Em outras palavras, os renderizadores de item são um lugar onde otimizar o seu código JavaScript realmente conta.
Renderizadores básicos
Vamos começar com rápida análise do que é uma função de renderização de item — que chamarei simplesmente de renderizador. Um renderizador é uma função designada por você à propriedade itemTemplate do ListView, em vez de um nome do modelo, e essa função será utilizada, conforme necessário, para itens que o ListView quer incluir no DOM. (Observe que as informações básicas para renderizadores podem ser encontradas na página itemTemplate, mas é a amostra que realmente apresenta as otimizações.)
Você pode esperar que uma função de renderização de item seria simplesmente designada a um item a partir da fonte de dados do ListView. Ela, então, criaria os elementos HTML necessários para aquele item específico e retornaria o elemento raiz que o ListView pode adicionar ao DOM. Basicamente, é isto que acontece, mas existem duas considerações adicionais. Primeiro, os próprios dados do item podem ser carregados de forma assíncrona, portanto, faz sentido associar a criação do elemento à disponibilidade daqueles dados. Além disso, o próprio processo de renderização do item pode envolver trabalhos assíncronos, como carregar imagens de URIs remotos ou a leitura de dados em outros arquivos identificados nos dados do item. Os diferentes níveis de otimização que analisaremos, na verdade, permitem um volume arbitrário de trabalho assíncrono entre a solicitação dos elementos do item e a entrega, de fato, desses elementos.
Portanto, repito, você pode confiar que promessas estarão envolvidas! Primeiro porque o ListView não fornece ao renderizador apenas os dados do item diretamente, ele fornece uma promessa para esses dados. Em vez da função retornar diretamente ao elemento raiz do item, ela retorna uma promessa para aquele elemento. Isto permite que o ListView agrupe diversas promessas de renderização de item e aguarde (de forma assíncrona) até que a página inteira de itens tenha sido renderizada. Na verdade, ele faz isso para gerenciar, de maneira inteligente, como ele constrói as diferentes páginas. Primeiro, construindo a página de itens visíveis. Depois, construindo duas páginas não exibidas na tela, para frente e para trás, onde é mais provável que os usuários façam uma panorâmica para avançar. Além disso, ter todas estas promessas nos devidos lugares significa que o ListView pode cancelar facilmente a renderização de itens inacabados, caso o usuário deixe de fazer a panorâmica, evitando assim, a criação desnecessária de elementos.
Podemos ver como essas promessas são usadas na função simpleRenderer da amostra:
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; }); }
Este código primeiro anexa um manipulador concluído ao itemPromise. O manipulador é chamado quando os dados do item estão disponíveis e efetivamente cria os elementos em resposta. Observe, novamente, que não estamos retornando o elemento diretamente. Estamos retornando uma promessa que é cumprida com esse elemento. Ou seja, o valor de retorno do itemPromise.then() é uma promessa que é cumprida com o element se e quando o ListView precisar dele.
Retornar uma promessa nos permite realizar outras tarefas assíncronas, se necessário. Neste caso, o renderizador pode simplesmente encadear estas promessas intermediárias, retornando a promessa da última then na cadeia. Por exemplo:
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; }); }
Observe que, o dele é um caso onde não usamos done no final da cadeia, porque estamos retornando uma promessa da última chamada then. O ListView se responsabiliza por lidar com qualquer erro que possa surgir.
Renderizadores de espaço reservado
A próxima etapa da otimização do ListView utiliza um renderizador de espaço reservado que divide a construção do elemento em duas etapas. Isto permite que o ListView solicite apenas as partes de um elemento que são necessárias para definir o layout geral da lista, sem ter que construir todos os elementos dentro de cada item. Consequentemente, o ListView pode completar o seu cálculo de layout rapidamente e continuar a responder eficazmente a mais entradas. Ele pode, então, solicitar o restante do elemento mais tarde.
Um renderizador de espaço reservado retorna um objeto com duas propriedades, em vez de apenas uma promessa:
- element O elemento de nível superior na estrutura do item, que basta para definir o seu tamanho e forma, e que não depende dos dados do item.
- renderComplete Uma promessa que é cumprida quando o restante dos conteúdos do elemento são construídos, ou seja, retorna a promessa de qualquer cadeia que você tenha iniciado com o itemPromise.then, como anteriormente.
O ListView é inteligente o bastante para verificar se o seu renderizador retornou uma promessa (o caso básico anterior) ou um objeto com propriedades element e renderComplete (casos mais avançados). Portanto, o renderizador de espaço reservado equivalente (na amostra) para o simpleRenderer anterior é o seguinte:
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' />"); }) }; }
Observe que a atribuição element.innerHTML pode ser movida dentro do renderComplete porque a classe itemTempl no arquivo de amostra css/scenario1.css especificou diretamente a largura e a altura do item. A razão para ela estar incluída na propriedade element é porque ela fornece o padrão de texto “…” no espaço reservado. Você também poderia facilmente utilizar um elemento img que se refira a um pequeno recurso no pacote que é compartilhado por todos os itens e, portanto, seja renderizado rapidamente.
Reciclar renderizadores de espaço reservado
A próxima otimização, reciclar renderizadores de espaço reservado não adiciona nada de novo no que se refere às promessas. Ela adiciona o reconhecimento de um segundo parâmetro para o renderizador chamado recycled, que é o elemento raiz de um item que foi anteriormente renderizado, mas não está mais visível. Ou seja, o elemento reciclado já possui os seus elementos filho nos devidos lugares, para que você possa substituir os dados ou talvez alterar alguns desses elementos. Isto evita a maioria das solicitações caras de criação de elementos, necessárias para um item totalmente novo, economizando um tempo significativo no processo de renderização.
O ListView pode fornecer um elemento reciclado quando o seu loadingBehavior estiver configurado para "randomaccess". Se recycled for fornecido, você pode simplesmente limpar dados do elemento (e dos seus filhos), retorná-lo ao seu espaço reservado e, então, preencher os dados e criar filhos adicionais (se necessário) dentro do renderComplete. Se um elemento reciclado não for fornecido (como acontece quando o ListView é criado pela primeira vez ou quando o loadingBehavior for "incremental"), você criará o elemento novamente. Aqui está o código da amostra para esta variação:
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"; }) }; }
Na opção renderComplete, certifique-se de verificar a existência de elementos não criados para um novo espaço reservado, como por exemplo label, e crie-os aqui, se necessário.
Se você deseja livrar-se de itens reciclados de forma mais generalizada, é possível fornecer uma função à propriedade resetItem do ListView. Esta função pode conter código similar ao mostrado acima. O mesmo vale para a propriedade resetGroupHeader , já que você pode usar funções modelo para cabeçalhos de grupo e também para itens. Não nos referimos muito a isto porque os cabeçalhos de grupos são em número muito menor e, normalmente, não têm as mesmas implicações no desempenho. Não obstante, a funcionalidade existe.
Renderizadores de várias etapas
Isto nos trás à penúltima otimização, o renderizador de várias etapas. Ele estende o renderizador de reciclagem de espaço reservado para atrasar o carregamento de imagens e outras mídias, até que o resto do item esteja completamente presente no DOM. Ele também atrasa efeitos, como animações, até que o item esteja realmente na tela. Isto reconhece que os usuários frequentemente fazem panorâmica bem rápidas no ListView. Portanto, faz sentido adiar as operações mais caras até o ListView ter chegado a uma posição estável.
O ListView fornece os ganchos necessários conforme os membros no item resultem do itemPromise: uma propriedade chamada ready (uma promessa) e dois métodos, o loadImage e o isOnScreen, retornam (ainda mais!) promessas. Ou seja:
renderComplete: itemPromise.then(function (item) { // item.ready, item.loadImage, and item.isOnScreen available })
Aqui está como usá-los:
- ready Retorna esta promessa a partir do primeiro manipulador concluído na sua cadeia. Essa promessa é cumprida quando a estrutura completa do elemento foi renderizada e está visível. Isto significa que você pode encadear um outro then com um manipulador concluído no qual você executa tarefas após a visibilidade, como, por exemplo carregar imagens.
- loadImage Baixa uma imagem de um URI e a exibe no elemento img designado, retornando uma promessa que é cumprida com esse mesmo elemento. Você anexa um manipulador concluído à esta promessa e ele mesmo retorna a promessa da isOnScreen. Observe que o loadImage criará um elemento img caso ele não seja fornecido e entregue a seu manipulador concluído.
- isOnScreen Retorna uma promessa cujo valor de cumprimento é uma parâmetro booliano indicando se o item está visível ou não. Nas implementações correntes, este é um valor conhecido, portanto, a promessa é cumprida de forma síncrona. Entretanto, encapsulando-o em uma promessa, ele pode ser usado em uma cadeia mais longa.
Pode-se observar tudo isto na função multistageRenderer da amostra, onde a conclusão do carregamento da imagem é usada para dar início a uma animação de fade-in. Aqui estou apenas mostrando o que foi retornado da promessa 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); } })
Embora haja muita coisa acontecendo, ainda temos apenas uma cadeia de promessas básica. A primeira operação assíncrona no renderizador atualiza partes simples da estrutura do elemento do item, tais como texto. Ela, então, retorna a promessa em item.ready. Quando a promessa é cumprida — ou, mais precisamente, se a promessa é cumprida — utilizamos o método assíncrono loadImage do item para dar início ao download de uma imagem, retornando a promessa item.isOnScreen daquele manipulador concluído. Isto significa que o sinalizador de visibilidade onscreen é passado para o manipulador concluído final na cadeia. Quando e se aquela promessa isOnScreen é cumprida — o que significa que o item está realmente visível — podemos executar operações relevantes como, por exemplo, animações.
Enfatizo a parte "se", porque pode ser que, novamente, aquele usuário esteja fazendo uma panorâmica dentro do ListView enquanto isto está acontecendo. Ter todas estas promessas encadeadas torna possível para o ListView cancelar operações assíncronas, sempre que estes itens não estejam visíveis e/ou desativados de uma página com buffer. Basta dizer que, o controle do ListView passou por muitos testes de desempenho!
Também é importante lembrar, novamente, que estamos usando o then em todas estas cadeias, porque ainda estamos retornando uma promessa da função de renderização dentro da propriedade renderComplete. Nunca somos o final da cadeia destes renderizadores, portanto, nunca usaremos o done no final.
Agrupamento de miniaturas
A última otimização é, verdadeiramente, o coup de grace para o controle do ListView. Na função chamada batchRenderer, encontramos esta estrutura para o renderComplete (com a maior parte do código omitido):
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); } })
Isto é quase o mesmo que o multistageRenderer com exceção da misteriosa solicitação para a inserção de uma função chamada thumbnailBatch entre a solicitação item.loadImage e a verificação do item.isOnScreen. O posicionamento do thumbnailBatch na cadeia indica que o seu valor de retorno deve ser um manipulador concluído, que retorna uma outra promessa.
Confuso? Não se preocupe, chegaremos ao fundo disto! Mas antes precisamos de um pouco mais de informações sobre o que estamos tentando fazer.
Se tivéssemos apenas um ListView, com um único item, várias otimizações de carregamento não seriam percebidas. Mas os ListViews normalmente têm vários itens e a função de renderização é necessária para cada um deles. Na opção multistageRenderer da seção anterior, e renderização de cada item dá início a uma operação assíncrona item.loadImage para baixar a sua miniatura de um URI arbitrário, e cada operação pode levar um período de tempo arbitrário. Portanto, para a lista completa, poderemos ter uma série de solicitações simultâneas de loadImage ocorrendo, com a renderização de cada item aguardando a conclusão da sua miniatura específica. Até aqui, tudo bem.
Entretanto, uma característica importante que não está visível no multistageRenderer é que o elemento img para a miniatura já está no DOM, e a função loadImage configura o atributo src daquela imagem assim que o download tiver concluído. Por sua vez, isto aciona uma atualização no mecanismo de renderização assim que retornarmos do restante da cadeia de promessas que, depois disso, é essencialmente síncrona.
É possível, então, que uma série de miniaturas possam voltar ao thread da interface do usuário dentro um curto período de tempo. Isso causará excesso de rotatividade no mecanismo de renderização e um fraco desempenho visual. Para evitar essa rotatividade o que queremos é criar por completo estes elements img antes deles estarem no DOM, e depois adicioná-los em lotes, de tal maneira que sejam todos manipulados em uma única passagem de renderização.
A amostra realiza isto por meio de um código de promessa mágico, uma função chamada createBatch. createBatch é solicitado apenas uma vez para todo o aplicativo e o seu resultado — uma outra função — é armazenado na variável chamada thumbnailBatch:
var thumbnailBatch; thumbnailBatch = createBatch();
Uma solicitação à esta função thumbnailBatch , como a chamarei daqui para frente, é novamente inserida na cadeia de promessa de renderizador. O propósito dessa inserção, dada a natureza do código de loteamento, como logo veremos, é agrupar um conjunto de elementos img carregados, liberando-os para mais processamento em intervalos adequados. Repetindo, apenas ao olhar para a cadeia de promessa no renderizador, uma solicitação ao thumbnailBatch() deve retornar uma função concluída de manipulador que retorna uma promessa, e o valor de cumprimento dessa promessa (analisando a próxima etapa na cadeia) deve ser um elemento img que pode ser adicionado ao DOM. Ao adicionar estas imagens ao DOM, após o loteamento ter sido concluído, combinamos todo este grupo para a mesma passagem de renderização.
Esta é uma importante diferença entre o batchRenderer e o multistageRenderer da seção anterior: no último o elemento da miniatura img já existe no DOM e é passado ao loadImage como um segundo parâmetro. Portanto, quando loadImage configura o atributo src da imagem, uma atualização de renderização é acionada. Entretanto, dentro do batchRenderer, este elemento img é criado separadamente dentro do loadImage (onde o src também é configurado), mas o img ainda não está no DOM. Ele só é adicionado ao DOM após a conclusão da etapa do thumbnailBatch , tornando-o parte de um grupo dentro daquele cálculo de layout único.
Agora, vamos ver como funciona este loteamento. Aqui está a função createBatch completa:
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; }); }; }; }
Novamente, o createBatch é solicitado apenas uma vez e o resultado do seu thumbnailBatch é solicitado para cada item renderizadona lista. O manipulador concluído que o thumbnailBatch cria é, então, solicitado sempre que uma operação loadImage é concluída.
Este manipulador concluído poderia também ter sido diretamente inserido na função de renderização, mas o que estamos tentando fazer aqui é coordenar atividades em vários itens em vez de um item de cada vez. Esta coordenação é obtida por meio de duas variáveis criadas e inicializadas no início do createBatch: batchedTimeout, inicializado como uma promessa vazia e batchedItems, inicializado como uma matriz de funções que, inicialmente, está vazia. createBatch também declara uma função completeBatch, que simplesmente descarta os batchedItems, solicitando cada função na matriz:
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; }); }; }; }
Agora, vejamos o que acontece dentro do thumbnailBatch (a função retornada do createBatch), que é novamente solicitada para cada item sendo renderizado. Primeiro, cancelamos qualquer batchedTimeout existente e imediatamente o recriamos:
batchTimeout.cancel(); batchTimeout = WinJS.Promise.timeout(waitPeriod || 64).then(completeBatch);
A segunda linha mostra o padrão futuro de entrega/cumprimento que discutimos na postagem All About Promises <TODO: link>: ele diz para solicitar o completeBatch após um atraso de waitPeriod milissegundos (com um padrão de 64ms). Isto significa que enquanto o thumbnailBatch estiver sendo solicitado novamente dentro do waitPeriod de uma solicitação anterior, o batchTimeout será redefinido para um outro waitPeriod. E, como o thumbnailBatch só é solicitado após que uma solicitação item.loadImage seja concluída, estamos, na verdade dizendo que quaisquer operações loadImage que forem concluídas dentro do waitPeriod da anterior, será incluída no mesmo lote. Quando existe uma lacuna maior do que o waitPeriod, o lote é processado — imagens são adicionadas ao DOM — e o próximo lote começa.
Após manipular este tempo de espera, o thumbnailBatch cria uma nova promessa que simplesmente envia a função despachante concluída para dentro da matriz batchedItems:
var delayedPromise = new WinJS.Promise(function (c) { batchedItems.push(c); });
Lembre-se do All About Promises <TODO: link> que uma promessa é apenas uma construção de código e isso é tudo o que temos aqui. A nova promessa criada não possui um comportamento assíncrono por si própria: estamos apenas adicionando a função despachante concluída c, para o batchedItems. Obviamente, não fazemos nada com o despachante até que o batchedTimeout seja concluído de forma assíncrona, portanto, existe de fato, um relacionamento assíncrono aqui. Quando acontece o tempo de espera e limpamos o lote (dentro do completeBatch), invocaremos quaisquer manipuladores concluídos que tenham sido dados ao delayedPromise.then.
Isto nos traz às últimas linhas de código no createBatch, que é a função retornada pelo thumbnailBatch. Essa função é exatamente o manipulador concluído que é inserido dentro de toda a cadeia de promessa do renderizador:
return function (v) { return delayedPromise.then(function () { return v; }); };
Na verdade, vamos inserir este código diretamente na cadeia de promessa para vermos os relacionamentos resultantes:
return item.loadImage(item.data.thumbnail); }).then(function (v) { return delayedPromise.then(function () { return v; }); ).then(function (newimg) {
Agora, podemos ver que o argumento v é o resultado do item.loadImage, que é o elemento img criado para nós. Se não quiséssemos fazer o loteamento, bastaria dizer return WinJS.Promise.as(v) e a cadeia inteira ainda funcionaria: v seria, então, passado de forma assíncrona e seria exibido como newimg na etapa seguinte.
Entretanto, em vez disso, estamos retornando a promessa do delayedPromise.then que não será cumprida — com v — até que a atual batchedTimeout seja cumprida. Neste momento — quando houver novamente uma lacuna de waitPeriod entre as conclusões do loadImage —, estes elementos img serão entregues à etapa seguinte na cadeia, onde serão adicionados ao DOM.
E é isso!
Conclusão
As cinco diferentes funções de renderização demonstradas na amostra de desempenho de otimização do ListView em HTML têm uma coisa em comum: elas mostram como o relacionamento assíncrono entre o ListView e o renderizador — expressado por meio de promessas — possibilita uma enorme flexibilidade ao renderizador sobre como e quando ele produz elementos para os itens na lista. Ao escrever os seus próprios aplicativos, a estratégia a ser usada para a otimização do ListView depende muito do tamanho da sua fonte de dados, da complexidade dos próprios itens e do volume de dados que você está obtendo de forma assíncrona para esses itens (como baixar imagens remotas). Com certeza, você desejará manter os renderizadores de itens o mais simples que puder, para ainda atender os seus objetivos de desempenho. Em todo caso, você agora tem todas as ferramentas de que precisa para ajudar o ListView e o seu aplicativo a alcançarem o melhor desempenho.
Kraig Brockschmidt
Gerente de programas, equipe de ecossistema do Windows
Autor, Programming Windows 8 Apps in HTML, CSS, and JavaScript (Programando aplicativos do Windows 8 em HTML, CSS e JavaScript)