Partilhar via


Evite usar o método context.sync em loops

Observação

Este artigo pressupõe que você esteja além do estágio inicial de trabalho com pelo menos uma das quatro APIs JavaScript do Office específicas do aplicativo — para Excel, Word, OneNote e Visio — que usam um sistema em lote para interagir com o documento do Office. Em particular, você deve saber o que uma chamada de context.sync faz e você deve saber o que é um objeto de coleção. Se você não estiver nessa fase, comece com a compreensão da API JavaScript do Office e da documentação vinculada em "específico do aplicativo" nesse artigo.

Para alguns cenários de programação em Suplementos do Office que usam um dos modelos de API específicos do aplicativo (para Excel, Word, PowerPoint, OneNote e Visio), seu código precisa ler, gravar ou processar alguma propriedade de cada membro de um objeto de coleção. Por exemplo, um suplemento do Excel que precisa obter os valores de cada célula em uma determinada coluna de tabela ou um suplemento Word que precisa realçar cada instância de uma cadeia de caracteres no documento. Você precisa iterar sobre os membros na items propriedade do objeto de coleção; mas, por razões de desempenho, você precisa evitar chamar context.sync em cada iteração do loop. Cada chamada de context.sync é uma ida e volta do suplemento ao documento do Office. Viagens de ida e volta repetidas prejudicam o desempenho, especialmente se o suplemento estiver em execução em Office na Web porque as viagens de ida e volta atravessam a Internet.

Observação

Todos os exemplos neste artigo usam for loops, mas as práticas descritas se aplicam a qualquer instrução de loop que possa iterar por meio de uma matriz, incluindo o seguinte:

  • for
  • for of
  • while
  • do while

Eles também se aplicam a qualquer método de matriz ao qual uma função é passada e aplicada aos itens na matriz, incluindo o seguinte:

  • Array.every
  • Array.forEach
  • Array.filter
  • Array.find
  • Array.findIndex
  • Array.map
  • Array.reduce
  • Array.reduceRight
  • Array.some

Escrevendo no documento

No caso mais simples, você só está escrevendo para membros de um objeto de coleção, não lendo suas propriedades. Por exemplo, o código a seguir realça em amarelo cada instância do "o" em um documento Word.

Observação

Geralmente, é uma boa prática colocar uma final context.sync pouco antes do caractere "}" de fechamento da função de aplicativo run (como Excel.run, Word.run, etc.). Isso ocorre porque a run função faz uma chamada oculta de context.sync como a última coisa que faz se, e somente se, houver comandos enfileirados que ainda não foram sincronizados. O fato de essa chamada estar oculta pode ser confuso, portanto, geralmente recomendamos que você adicione o explícito context.sync. No entanto, dado que este artigo trata da minimização de chamadas de context.sync, na verdade, é mais confuso adicionar uma final context.synctotalmente desnecessária . Portanto, neste artigo, deixamos de fora quando não há comandos não sincronizados no final do run.

await Word.run(async function (context) {
  let startTime, endTime;
  const docBody = context.document.body;

  // search() returns an array of Ranges.
  const searchResults = docBody.search('the', { matchWholeWord: true });
  searchResults.load('font');
  await context.sync();

  // Record the system time.
  startTime = performance.now();

  for (let i = 0; i < searchResults.items.length; i++) {
    searchResults.items[i].font.highlightColor = '#FFFF00';

    await context.sync(); // SYNCHRONIZE IN EACH ITERATION
  }
  
  // await context.sync(); // SYNCHRONIZE AFTER THE LOOP

  // Record the system time again then calculate how long the operation took.
  endTime = performance.now();
  console.log("The operation took: " + (endTime - startTime) + " milliseconds.");
})

O código anterior levou 1 segundo completo para ser concluído em um documento com 200 instâncias de "o" em Word no Windows. Mas quando a await context.sync(); linha dentro do loop é comentada e a mesma linha logo após o loop não ser descompactado, a operação levou apenas um 1/10 de segundo. Em Word na Web (com o Edge como o navegador), levou três segundos completos com a sincronização dentro do loop e apenas 6/10 de segundo com a sincronização após o loop, cerca de cinco vezes mais rápido. Em um documento com 2.000 instâncias de "the", ele levou (em Word na Web) 80 segundos com a sincronização dentro do loop e apenas 4 segundos com a sincronização após o loop, cerca de 20 vezes mais rápido.

Observação

Vale a pena perguntar se a versão synchronize-inside-the-loop seria executada mais rapidamente se as sincronizações fossem executadas simultaneamente, o que poderia ser feito simplesmente removendo o await palavra-chave da frente do context.sync(). Isso faria com que o runtime iniciasse a sincronização e iniciasse imediatamente a próxima iteração do loop sem aguardar a conclusão da sincronização. No entanto, essa não é uma solução tão boa quanto mover o context.sync fora do loop inteiramente por esses motivos.

  • Assim como os comandos em um trabalho em lote de sincronização são enfileirados, os trabalhos em lote em si são enfileirados no Office, mas o Office dá suporte a não mais de 50 trabalhos em lote na fila. Mais erros de gatilho. Portanto, se houver mais de 50 iterações em um loop, há uma chance de que o tamanho da fila seja excedido. Quanto maior o número de iterações, maior a chance disso acontecer.
  • "Simultaneamente" não significa simultaneamente. Ainda levaria mais tempo para executar várias operações de sincronização do que para executar uma.
  • As operações simultâneas não são garantidas para serem concluídas na mesma ordem em que começaram. No exemplo anterior, não importa qual ordem a palavra "o" seja realçada, mas há cenários em que é importante que os itens da coleção sejam processados em ordem.

Ler valores do documento com o padrão de loop dividido

context.syncEvitar s dentro de um loop torna-se mais desafiador quando o código deve ler uma propriedade dos itens de coleção à medida que processa cada um deles. Suponha que seu código precise iterar todos os controles de conteúdo em um documento Word e registrar o texto do primeiro parágrafo associado a cada controle. Seus instintos de programação podem levá-lo a fazer loop sobre os controles, carregar a text propriedade de cada parágrafo (primeiro), chamar context.sync para preencher o objeto de parágrafo proxy com o texto do documento e, em seguida, registrá-lo. Apresentamos um exemplo a seguir.

Word.run(async (context) => {
    const contentControls = context.document.contentControls.load('items');
    await context.sync();

    for (let i = 0; i < contentControls.items.length; i++) {
      const paragraph = contentControls.items[i].getRange('Whole').paragraphs.getFirst();
      paragraph.load('text');
      await context.sync();
      console.log(paragraph.text);
    }
});

Nesse cenário, para evitar ter um context.sync em um loop, você deve usar um padrão que chamamos de padrão de loop dividido . Vamos ver um exemplo concreto do padrão antes de chegarmos a uma descrição formal dele. Veja como o padrão de loop de divisão pode ser aplicado ao snippet de código anterior. Observe o seguinte sobre este código.

  • Agora há dois loops e o context.sync vem entre eles, portanto, não há nenhum context.sync dentro de qualquer loop.
  • O primeiro loop itera através dos itens no objeto de coleção e carrega a text propriedade da mesma forma que o loop original, mas o primeiro loop não pode registrar o texto do parágrafo porque ele não contém mais um context.sync para preencher a text propriedade do paragraph objeto proxy. Em vez disso, ele adiciona o paragraph objeto a uma matriz.
  • O segundo loop itera pela matriz criada pelo primeiro loop e registra o text de cada paragraph item. Isso é possível porque o context.sync que veio entre os dois loops preencheu todas as text propriedades.
Word.run(async (context) => {
    const contentControls = context.document.contentControls.load("items");
    await context.sync();

    const firstParagraphsOfCCs = [];
    for (let i = 0; i < contentControls.items.length; i++) {
      const paragraph = contentControls.items[i].getRange('Whole').paragraphs.getFirst();
      paragraph.load('text');
      firstParagraphsOfCCs.push(paragraph);
    }

    await context.sync();

    for (let i = 0; i < firstParagraphsOfCCs.length; i++) {
      console.log(firstParagraphsOfCCs[i].text);
    }
});

O exemplo anterior sugere o procedimento a seguir para transformar um loop que contém um context.sync no padrão de loop dividido.

  1. Substitua o loop por dois loops.
  2. Create um primeiro loop para iterar sobre a coleção e adicionar cada item a uma matriz, ao mesmo tempo em que carrega qualquer propriedade do item que seu código precisa ler.
  3. Após o primeiro loop, chame context.sync para preencher os objetos proxy com quaisquer propriedades carregadas.
  4. Siga o context.sync com um segundo loop para iterar sobre a matriz criada no primeiro loop e ler as propriedades carregadas.

Processar objetos no documento com o padrão de objetos correlacionados

Vamos considerar um cenário mais complexo em que o processamento dos itens na coleção requer dados que não estão nos itens em si. O cenário prevê um suplemento Word que opera em documentos criados a partir de um modelo com algum texto de caldeira. Dispersos no texto estão uma ou mais instâncias das seguintes cadeias de caracteres de espaço reservado: "{Coordinator}", "{Deputy}" e "{Manager}". O suplemento substitui cada espaço reservado pelo nome de alguma pessoa. A interface do usuário do suplemento não é importante para este artigo. Por exemplo, ele pode ter um painel de tarefas com três caixas de texto, cada uma rotulada com um dos espaços reservados. O usuário insere um nome em cada caixa de texto e, em seguida, pressiona um botão Substituir . O manipulador do botão cria uma matriz que mapeia os nomes para os espaços reservados e, em seguida, substitui cada espaço reservado pelo nome atribuído.

Você não precisa realmente produzir um suplemento com essa interface do usuário para experimentar o código. Você pode usar a ferramenta Script Lab para protótipo do código importante. Use a instrução de atribuição a seguir para criar a matriz de mapeamento.

const jobMapping = [
        { job: "{Coordinator}", person: "Sally" },
        { job: "{Deputy}", person: "Bob" },
        { job: "{Manager}", person: "Kim" }
    ];

O código a seguir mostra como você pode substituir cada espaço reservado pelo nome atribuído se você usou context.sync em loops.

Word.run(async (context) => {

    for (let i = 0; i < jobMapping.length; i++) {
      let options = Word.SearchOptions.newObject(context);
      options.matchWildCards = false;
      let searchResults = context.document.body.search(jobMapping[i].job, options);
      searchResults.load('items');

      await context.sync(); 

      for (let j = 0; j < searchResults.items.length; j++) {
        searchResults.items[j].insertText(jobMapping[i].person, Word.InsertLocation.replace);

        await context.sync();
      }
    }
});

No código anterior, há um loop externo e interno. Cada um deles contém um context.sync. Com base no primeiro snippet de código neste artigo, você provavelmente verá que o context.sync no loop interno pode simplesmente ser movido para depois do loop interno. Mas isso ainda deixaria o código com um context.sync (dois deles na verdade) no loop externo. O código a seguir mostra como você pode remover context.sync dos loops. Discutiremos o código mais tarde.

Word.run(async (context) => {

    const allSearchResults = [];
    for (let i = 0; i < jobMapping.length; i++) {
      let options = Word.SearchOptions.newObject(context);
      options.matchWildCards = false;
      let searchResults = context.document.body.search(jobMapping[i].job, options);
      searchResults.load('items');
      let correlatedSearchResult = {
        rangesMatchingJob: searchResults,
        personAssignedToJob: jobMapping[i].person
      }
      allSearchResults.push(correlatedSearchResult);
    }

    await context.sync()

    for (let i = 0; i < allSearchResults.length; i++) {
      let correlatedObject = allSearchResults[i];

      for (let j = 0; j < correlatedObject.rangesMatchingJob.items.length; j++) {
        let targetRange = correlatedObject.rangesMatchingJob.items[j];
        let name = correlatedObject.personAssignedToJob;
        targetRange.insertText(name, Word.InsertLocation.replace);
      }
    }

    await context.sync();
});

Observe que o código usa o padrão de loop dividido.

  • O loop externo do exemplo anterior foi dividido em dois. (O segundo loop tem um loop interno, o que é esperado porque o código está iterando em um conjunto de trabalhos (ou espaços reservados) e dentro desse conjunto ele está iterando nos intervalos correspondentes.)
  • Há um context.sync após cada loop principal, mas não dentro context.sync de nenhum loop.
  • O segundo loop principal itera por meio de uma matriz criada no primeiro loop.

Mas a matriz criada no primeiro loop não contém apenas um objeto do Office, como o primeiro loop fez na seção Leitura de valores do documento com o padrão de loop dividido. Isso ocorre porque algumas das informações necessárias para processar os objetos Word Range não estão nos objetos Range em si, mas sim na jobMapping matriz.

Portanto, os objetos na matriz criada no primeiro loop são objetos personalizados que têm duas propriedades. O primeiro é uma matriz de intervalos de Word que correspondem a um título de trabalho específico (ou seja, uma cadeia de caracteres de espaço reservado) e o segundo é uma cadeia de caracteres que fornece o nome da pessoa atribuída ao trabalho. Isso torna o loop final fácil de gravar e fácil de ler, pois todas as informações necessárias para processar um determinado intervalo estão contidas no mesmo objeto personalizado que contém o intervalo. O nome que deve substituir correlatedObject.rangesMatchingJob.items[j] é a outra propriedade do mesmo objeto: correlatedObject.personAssignedToJob.

Chamamos essa variação do padrão de loop dividido de padrão de objetos correlacionados . A ideia geral é que o primeiro loop crie uma matriz de objetos personalizados. Cada objeto tem uma propriedade cujo valor é um dos itens em um objeto de coleção do Office (ou uma matriz desses itens). O objeto personalizado tem outras propriedades, cada uma das quais fornece informações necessárias para processar os objetos do Office no loop final. Consulte a seção Outros exemplos desses padrões para obter um link para um exemplo em que o objeto de correlação personalizado tem mais de duas propriedades.

Outra ressalva: às vezes, é preciso mais de um loop apenas para criar a matriz de objetos de correlação personalizados. Isso pode acontecer se você precisar ler uma propriedade de cada membro de um objeto de coleção do Office apenas para coletar informações que serão usadas para processar outro objeto de coleção. (Por exemplo, seu código precisa ler os títulos de todas as colunas em uma tabela do Excel porque o suplemento aplicará um formato de número às células de algumas colunas com base no título dessa coluna.) Mas você sempre pode manter o context.syncs entre os loops, em vez de em um loop. Consulte a seção Outros exemplos desses padrões para obter um exemplo.

Outros exemplos desses padrões

Quando você não deve usar os padrões neste artigo?

O Excel não pode ler mais de 5 MB de dados em uma determinada chamada de context.sync. Se esse limite for excedido, um erro será gerado. (Consulte a "seção suplementos do Excel" dos limites de recursos e otimização de desempenho para suplementos do Office para obter mais informações.) É muito raro que esse limite seja abordado, mas se houver uma chance de que isso aconteça com o suplemento, o código não deverá carregar todos os dados em um único loop e seguir o loop com um context.sync. Mas você ainda deve evitar ter uma context.sync em cada iteração de um loop sobre um objeto de coleção. Em vez disso, defina subconjuntos dos itens na coleção e loop sobre cada subconjunto por sua vez, com um context.sync entre os loops. Você pode estruturar isso com um loop externo que itera sobre os subconjuntos e contém o context.sync em cada uma dessas iterações externas.