Partilhar via


Evite usar o método context.sync em loops

Observação

Este artigo pressupõe que está para além da fase inicial de trabalhar com, pelo menos, uma das quatro APIs de JavaScript do Office específicas da aplicação , para o Excel, Word, OneNote e Visio, que utilizam um sistema de lotes para interagir com o documento do Office. Em particular, deve saber o que faz uma chamada e context.sync deve saber o que é um objeto de coleção. Se não estiver nessa fase, comece por Compreender a API javaScript do Office e a documentação associada a em "específico da aplicação" nesse artigo.

Os Suplementos do Office que utilizam um dos modelos de API específicos da aplicação podem ter cenários que requerem que o seu código leia ou escreva algumas propriedades de cada membro de um objeto de coleção. Por exemplo, um suplemento do Excel que obtém os valores de cada célula numa determinada coluna de tabela ou um suplemento Word que realça todas as instâncias de uma cadeia no documento. Terá de iterar os membros na propriedade do items objeto de coleção, mas, por motivos de desempenho, deve evitar chamar context.sync em todas as iterações do ciclo. Cada chamada de é uma viagem de context.sync ida e volta do suplemento para o documento do Office. As repetidas viagens de ida e volta prejudicam o desempenho, especialmente se o suplemento estiver a ser executado em Office na Web porque as viagens de ida e volta passam pela Internet.

Observação

Todos os exemplos neste artigo utilizam for ciclos, mas as práticas descritas aplicam-se a qualquer instrução de ciclo que possa iterar através de uma matriz, incluindo o seguinte:

  • for
  • for of
  • while
  • do while

Também se aplicam a qualquer método de matriz ao qual uma função é transmitida 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

Observação

Geralmente, é uma boa prática colocar uma final context.sync imediatamente antes do caráter "}" de fecho da função da aplicação run (como Excel.run, Word.run, etc.). Isto acontece porque a run função faz uma chamada oculta de context.sync como a última coisa que faz se, e apenas se, existirem comandos em fila que ainda não foram sincronizados. O facto de esta chamada estar oculta pode ser confuso, pelo que geralmente recomendamos que adicione o .context.sync No entanto, dado que este artigo se trata de minimizar as chamadas de context.sync, na verdade, é mais confuso adicionar uma final context.synctotalmente desnecessária. Por isso, neste artigo, vamos deixá-lo de fora quando não existem comandos não sincronizados no final do run.

Escrever no documento

No caso mais simples, só está a escrever para os membros de um objeto de coleção e não para ler as respetivas propriedades. Por exemplo, o seguinte código realça em amarelo todas as instâncias de "o" num documento Word.

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 demorou 1 segundo a ser concluído num documento com 200 instâncias de "o" no Word no Windows. No entanto, quando a await context.sync(); linha dentro do ciclo é comentada e a mesma linha logo após o ciclo não estar consolidado, a operação demorou apenas um décimo de segundo. No Word na Web (com o Edge como browser), demorou 3 segundos completos com a sincronização dentro do ciclo e apenas 6/10 segundos com a sincronização após o ciclo, cerca de cinco vezes mais rápido. Num documento com 2000 instâncias de "o", demorou (no Word na Web) 80 segundos com a sincronização dentro do ciclo e apenas 4 segundos com a sincronização após o ciclo, 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 em simultâneo, o que poderia ser feito simplesmente removendo o await palavra-chave da frente do context.sync(). Isto faria com que o runtime iniciasse a sincronização e, em seguida, iniciasse imediatamente a próxima iteração do ciclo sem esperar que a sincronização fosse concluída. No entanto, esta não é uma solução tão boa como mover completamente o context.sync ciclo para fora do ciclo pelos seguintes motivos.

  • Tal como os comandos numa tarefa de lote de sincronização são colocados em fila, os trabalhos em lote são colocados em fila no Office, mas o Office não suporta mais de 50 tarefas em lote na fila. Mais erros de acionadores. Assim, se existirem mais de 50 iterações num ciclo, existe a possibilidade de o tamanho da fila ser excedido. Quanto maior for o número de iterações, maior é a probabilidade de isto acontecer.
  • "Simultaneamente" não significa simultaneamente. Ainda demoraria mais tempo a executar múltiplas operações de sincronização do que a executar uma.
  • Não é garantido que as operações simultâneas sejam concluídas pela mesma ordem em que foram iniciadas. No exemplo anterior, não importa a ordem pela qual a palavra "o" é realçada, mas existem cenários em que é importante que os itens na coleção sejam processados por ordem.

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

Evitar dentro de context.sync um ciclo torna-se mais desafiante quando o código tem de ler uma propriedade dos itens da coleção à medida que processa cada um deles. Suponha que o código tem de iterar todos os controlos de conteúdo num documento Word e registar o texto do primeiro parágrafo associado a cada controlo. Os seus instintos de programação podem levar-no a repetir os controlos, 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, registá-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++) {
      // The sync statement in this loop will degrade performance.
      const paragraph = contentControls.items[i].getRange('Whole').paragraphs.getFirst(); 
      paragraph.load('text');
      await context.sync();
      console.log(paragraph.text);
    }
});

Neste cenário, para evitar ter um context.sync ciclo, deve utilizar um padrão a que chamamos padrão de ciclo dividido . Vejamos um exemplo concreto do padrão antes de obtermos uma descrição formal do mesmo. Eis como o padrão de ciclo dividido pode ser aplicado ao fragmento de código anterior. Observe o seguinte sobre este código.

  • Existem agora dois ciclos e o context.sync que se encontra entre eles, pelo que não existe nenhum context.sync ciclo dentro de qualquer um dos ciclos.
  • O primeiro ciclo itera os itens no objeto de coleção e carrega a text propriedade, tal como o ciclo original, mas o primeiro ciclo não consegue registar o texto do parágrafo porque já não contém um context.sync para preencher a text propriedade do paragraph objeto proxy. Em vez disso, adiciona o paragraph objeto a uma matriz.
  • O segundo ciclo itera através da matriz que foi criada pelo primeiro ciclo e regista o text de cada paragraph item. Isto é possível porque o context.sync que veio entre os dois ciclos 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 seguinte procedimento para transformar um ciclo que contém um context.sync no padrão de ciclo dividido.

  1. Substitua o ciclo por dois ciclos.
  2. Crie um primeiro ciclo para iterar sobre a coleção e adicionar cada item a uma matriz, ao mesmo tempo que carrega qualquer propriedade do item que o seu código precisa de ler.
  3. Siga o primeiro ciclo com context.sync para preencher os objetos proxy com quaisquer propriedades carregadas.
  4. Siga o context.sync com um segundo ciclo para iterar sobre a matriz criada no primeiro ciclo 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 propriamente ditos. O cenário prevê uma Word suplemento que funciona em documentos criados a partir de um modelo com algum texto automático. Dispersos no texto estão uma ou mais instâncias das seguintes cadeias de marcador de posição: "{Coordinator}", "{Deputy}" e "{Manager}". O suplemento substitui cada marcador de posição pelo nome de uma pessoa. Embora a IU do suplemento não seja importante para este artigo, o suplemento pode ter um painel de tarefas com três caixas de texto, cada uma etiquetada com um dos marcadores de posição. O utilizador introduz um nome em cada caixa de texto e, em seguida, prime um botão Substituir . O processador do botão cria uma matriz que mapeia os nomes para os marcadores de posição e, em seguida, substitui cada marcador de posição pelo nome atribuído.

Não precisa de produzir um suplemento com esta IU para experimentar o código. Pode utilizar a ferramenta Script Lab para criar protótipos do código importante. Utilize a seguinte instrução de atribuição para criar a matriz de mapeamento.

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

O código seguinte mostra como pode substituir cada marcador de posição pelo respetivo nome atribuído se tiver utilizado context.sync ciclos internos.

Word.run(async (context) => {
    // The context.sync calls in the loops will degrade performance.
    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, existe um ciclo externo e interno. Cada um contém uma context.sync chamada. Com base no primeiro fragmento de código neste artigo, provavelmente verá que o context.sync ciclo interno pode simplesmente ser movido após o ciclo interno. No entanto, isso continuaria a deixar o código com um context.sync (dois deles na verdade) no ciclo externo. O código seguinte mostra como pode remover context.sync dos ciclos. Vamos falar sobre 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();
});

Tenha em atenção que o código utiliza o padrão de ciclo dividido.

  • O ciclo externo do exemplo anterior foi dividido em dois. (O segundo ciclo tem um ciclo interno, o que é esperado porque o código está a iterar sobre um conjunto de tarefas (ou marcadores de posição) e nesse conjunto está a iterar sobre os intervalos correspondentes.)
  • Existe um context.sync ciclo após cada ciclo principal, mas não context.sync dentro de qualquer ciclo.
  • O segundo ciclo principal itera através de uma matriz criada no primeiro ciclo.

No entanto, a matriz criada no primeiro ciclo não contém apenas um objeto do Office, como fez o primeiro ciclo na secção Valores de leitura do documento com o padrão de ciclo dividido. Isto deve-se ao facto de algumas das informações necessárias para processar os objetos Word Intervalo não se encontrarem nos próprios objetos Intervalo, mas sim da jobMapping matriz.

Assim, os objetos na matriz criada no primeiro ciclo são objetos personalizados que têm duas propriedades. A primeira é uma matriz de intervalos de Word que correspondem a um cargo específico (ou seja, uma cadeia de marcador de posição) e a segunda é uma cadeia que fornece o nome da pessoa atribuída à tarefa. Isto torna o ciclo final fácil de escrever e fácil de ler porque 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 a esta variação do padrão de ciclo dividido o padrão de objetos correlacionados . A ideia geral é que o primeiro ciclo cria uma matriz de objetos personalizados. Cada objeto tem uma propriedade cujo valor é um dos itens num 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 ciclo final. Veja a secção Outros exemplos destes padrões para obter uma ligação para um exemplo em que o objeto de correlação personalizada tem mais de duas propriedades.

Uma outra ressalva: por vezes, é preciso mais do que um ciclo apenas para criar a matriz de objetos correlacionados personalizados. Isto pode acontecer se precisar de ler uma propriedade de cada membro de um objeto de coleção do Office apenas para recolher informações que serão utilizadas para processar outro objeto de coleção. (Por exemplo, o código tem de ler os títulos de todas as colunas numa tabela do Excel, porque o seu suplemento vai aplicar um formato de número às células de algumas colunas com base no título dessa coluna.) No entanto, pode sempre manter o context.syncs entre os ciclos, em vez de num ciclo. Veja a secção Outros exemplos destes padrões para obter um exemplo.

Outros exemplos destes padrões

Quando não deve utilizar os padrões neste artigo?

O Excel não consegue ler mais de 5 MB de dados numa determinada chamada de context.sync. Se este limite for excedido, é gerado um erro. (Consulte a secção "Suplementos do Excel" de Limites de recursos e otimização de desempenho para Suplementos do Office para obter mais informações.) É muito raro que este limite seja abordado, mas se existir a possibilidade de tal acontecer com o seu suplemento, o código não deve carregar todos os dados num único ciclo e seguir o ciclo com um context.sync. No entanto, deve evitar ter uma context.sync em cada iteração de um ciclo sobre um objeto de coleção. Em vez disso, defina subconjunto dos itens na coleção e faça um ciclo sobre cada subconjunto, por sua vez, com um context.sync entre os ciclos. Pode estruturar isto com um ciclo externo que itera sobre os subconjuntos e contém o context.sync em cada uma destas iterações externas.