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.sync
totalmente 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.sync
Evitar 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á nenhumcontext.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 umcontext.sync
para preencher atext
propriedade doparagraph
objeto proxy. Em vez disso, ele adiciona oparagraph
objeto a uma matriz. - O segundo loop itera pela matriz criada pelo primeiro loop e registra o
text
de cadaparagraph
item. Isso é possível porque ocontext.sync
que veio entre os dois loops preencheu todas astext
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.
- Substitua o loop por dois loops.
- 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.
- Após o primeiro loop, chame
context.sync
para preencher os objetos proxy com quaisquer propriedades carregadas. - 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 dentrocontext.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.sync
s 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
- Para obter um exemplo muito simples para o Excel que usa loops
Array.forEach
, confira a resposta aceita para esta pergunta do Stack Overflow: é possível fazer fila mais de um contexto.load antes de context.sync? - Para obter um exemplo simples para Word que usa loops
Array.forEach
e não usaawait
async
/sintaxe, confira a resposta aceita para esta pergunta sobre o Stack Overflow: iterando todos os parágrafos com controles de conteúdo com a API JavaScript do Office. - Para obter um exemplo para Word que está escrito no TypeScript, consulte o exemplo Word Verificador de Estilo Angular2 do Suplemento, especialmente o arquivo word.document.service.ts. Ele tem uma mistura de
for
eArray.forEach
loops. - Para um exemplo de Word avançado, importe essa essência para a ferramenta Script Lab. Para obter contexto no uso da essência, consulte a resposta aceita para o Documento de pergunta de estouro de pilha que não está em sincronização após substituir texto. Este exemplo cria um tipo de objeto de correlação personalizado que tem três propriedades. Ele usa um total de três loops para construir a matriz de objetos correlacionados e mais dois loops para fazer o processamento final. Há uma mistura de
for
eArray.forEach
loops. - Embora não seja estritamente um exemplo dos padrões de loop dividido ou objetos correlacionados, há um exemplo avançado do Excel que mostra como converter um conjunto de valores de célula em outras moedas com apenas um único
context.sync
. Para experimentá-la, abra a ferramenta Script Lab e procure e navegue até o exemplo conversor de Conversor de Moedas.
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.