Éviter d’utiliser la méthode context.sync dans des boucles
Remarque
Cet article suppose que vous êtes au-delà de la phase de début de l’utilisation d’au moins l’une des quatre API JavaScript Office spécifiques à l’application (pour Excel, Word, OneNote et Visio) qui utilisent un système de traitement par lots pour interagir avec le document Office. En particulier, vous devez savoir ce que fait un appel de context.sync
et vous devez savoir ce qu’est un objet de collection. Si vous n’en êtes pas à ce stade, commencez par Comprendre l’API JavaScript Office et la documentation liée à sous « spécifique à l’application » dans cet article.
Pour certains scénarios de programmation dans les compléments Office qui utilisent l’un des modèles d’API spécifiques à l’application (pour Excel, Word, PowerPoint, OneNote et Visio), votre code doit lire, écrire ou traiter une propriété de chaque membre d’un objet de collection. Par exemple, un complément Excel qui doit obtenir les valeurs de chaque cellule d’une colonne de tableau particulière ou un complément Word qui doit mettre en surbrillance chaque instance d’une chaîne dans le document. Vous devez itérer sur les membres de la propriété de l’objet items
de collection ; mais, pour des raisons de performances, vous devez éviter d’appeler context.sync
dans chaque itération de la boucle. Chaque appel de context.sync
est un aller-retour entre le complément et le document Office. Les allers-retours répétés nuisent aux performances, en particulier si le complément s’exécute dans Office sur le Web parce que les allers-retours sont effectués sur Internet.
Remarque
Tous les exemples de cet article utilisent for
des boucles, mais les pratiques décrites s’appliquent à toute instruction de boucle qui peut itérer au sein d’un tableau, y compris les éléments suivants :
for
for of
while
do while
Elles s’appliquent également à toute méthode de tableau à laquelle une fonction est passée et appliquée aux éléments du tableau, notamment les éléments suivants :
Array.every
Array.forEach
Array.filter
Array.find
Array.findIndex
Array.map
Array.reduce
Array.reduceRight
Array.some
Écriture dans le document
Dans le cas le plus simple, vous écrivez uniquement dans les membres d’un objet de collection, sans lire leurs propriétés. Par exemple, le code suivant met en surbrillance en jaune chaque instance de « le » dans un document Word.
Remarque
Il est généralement recommandé de placer un caractère final context.sync
juste avant le caractère « } » fermant de la fonction d’application run
(par Excel.run
exemple, , Word.run
, etc.). Cela est dû au fait que la run
fonction effectue un appel masqué de context.sync
comme la dernière chose qu’elle fait si, et seulement si, il existe des commandes en file d’attente qui n’ont pas encore été synchronisées. Le fait que cet appel soit masqué peut prêter à confusion. Nous vous recommandons donc généralement d’ajouter le explicite context.sync
. Toutefois, étant donné que cet article concerne la réduction des appels de context.sync
, il est en fait plus déroutant d’ajouter un final context.sync
entièrement inutile. Par conséquent, dans cet article, nous l’excluons lorsqu’il n’y a pas de commandes non synchronisées à la fin de .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.");
})
Le code précédent a pris 1 seconde complète pour se terminer dans un document avec 200 instances de « the » dans Word sur Windows. Mais lorsque la await context.sync();
ligne à l’intérieur de la boucle est commentée et que la même ligne juste après la boucle est sans marques de commentaire, l’opération n’a pris que 1/10e de seconde. Dans Word sur le web (avec Edge comme navigateur), il a fallu 3 secondes complètes avec la synchronisation à l’intérieur de la boucle et seulement 6/10e de seconde avec la synchronisation après la boucle, environ cinq fois plus rapide. Dans un document avec 2 000 instances de « the », il a fallu (en Word sur le web) 80 secondes avec la synchronisation à l’intérieur de la boucle et seulement 4 secondes avec la synchronisation après la boucle, soit environ 20 fois plus rapidement.
Remarque
Il est utile de se demander si la version synchronize-inside-the-loop s’exécuterait plus rapidement si les synchronisations s’exécutaient simultanément, ce qui pourrait être fait en supprimant simplement le await
mot clé à l’avant du context.sync()
. Cela entraînerait le runtime à lancer la synchronisation, puis à démarrer immédiatement l’itération suivante de la boucle sans attendre la fin de la synchronisation. Toutefois, il ne s’agit pas d’une solution aussi bonne que de sortir entièrement de context.sync
la boucle pour ces raisons.
- Tout comme les commandes d’un travail de synchronisation par lots sont mises en file d’attente, les travaux par lots eux-mêmes sont mis en file d’attente dans Office, mais Office ne prend pas en charge plus de 50 travaux par lots dans la file d’attente. Toutes les autres déclenchent des erreurs. Par conséquent, s’il y a plus de 50 itérations dans une boucle, il est possible que la taille de la file d’attente soit dépassée. Plus le nombre d’itérations est élevé, plus il y a de chances que cela se produise.
- « Simultanément » ne signifie pas simultanément. L’exécution de plusieurs opérations de synchronisation prend encore plus de temps que l’exécution d’une seule.
- Il n’est pas garanti que les opérations simultanées se terminent dans l’ordre dans lequel elles ont démarré. Dans l’exemple précédent, peu importe l’ordre dans lequel le mot « le » est mis en surbrillance, mais il existe des scénarios où il est important que les éléments de la collection soient traités dans l’ordre.
Lire les valeurs du document avec le modèle de boucle fractionnée
L’évitement context.sync
de s à l’intérieur d’une boucle devient plus difficile lorsque le code doit lire une propriété des éléments de collection au fur et à mesure qu’il traite chacun d’eux. Supposons que votre code doit itérer tous les contrôles de contenu dans un document Word et journaliser le texte du premier paragraphe associé à chaque contrôle. Vos instincts de programmation peuvent vous amener à effectuer une boucle sur les contrôles, à charger la text
propriété de chaque (premier) paragraphe, à appeler context.sync
pour remplir l’objet de paragraphe proxy avec le texte du document, puis à le journaliser. Voici un exemple.
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);
}
});
Dans ce scénario, pour éviter d’avoir un context.sync
dans une boucle, vous devez utiliser un modèle que nous appelons le modèle de boucle fractionnée . Voyons un exemple concret du modèle avant d’en obtenir une description formelle. Voici comment appliquer le modèle de boucle fractionnée à l’extrait de code précédent. Notez ce qui suit à propos de ce code.
- Il y a maintenant deux boucles et le
context.sync
vient entre elles, donc il n’y a pascontext.sync
à l’intérieur de l’une ou l’autre boucle. - La première boucle itère dans les éléments de l’objet de collection et charge la
text
propriété comme la boucle d’origine, mais la première boucle ne peut pas enregistrer le texte du paragraphe, car elle ne contient plus decontext.sync
pour remplir latext
propriété de l’objetparagraph
proxy. Au lieu de cela, il ajoute l’objetparagraph
à un tableau. - La deuxième boucle itère dans le tableau qui a été créé par la première boucle et journalise le
text
de chaqueparagraph
élément. Cela est possible, car lecontext.sync
qui est venu entre les deux boucles remplissait toutes lestext
propriétés.
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);
}
});
L’exemple précédent suggère la procédure suivante pour transformer une boucle qui contient un context.sync
dans le modèle de boucle de fractionnement.
- Remplacez la boucle par deux boucles.
- Create une première boucle pour itérer sur la collection et ajouter chaque élément à un tableau tout en chargeant également toute propriété de l’élément que votre code doit lire.
- Après la première boucle, appelez
context.sync
pour remplir les objets proxy avec toutes les propriétés chargées. - Suivez le
context.sync
avec une deuxième boucle pour itérer sur le tableau créé dans la première boucle et lire les propriétés chargées.
Traiter des objets dans le document avec le modèle d’objets corrélés
Prenons l’exemple d’un scénario plus complexe où le traitement des éléments de la collection nécessite des données qui ne se trouvent pas dans les éléments eux-mêmes. Le scénario envisage un complément Word qui fonctionne sur les documents créés à partir d’un modèle avec du texte réutilisable. Dans le texte figurent une ou plusieurs instances des chaînes d’espace réservé suivantes : « {Coordinateur} », « {Deputy} » et « {Manager} ». Le complément remplace chaque espace réservé par le nom d’une personne. L’interface utilisateur du complément n’est pas importante pour cet article. Par exemple, il peut avoir un volet Office avec trois zones de texte, chacune étiquetée avec l’un des espaces réservés. L’utilisateur entre un nom dans chaque zone de texte, puis appuie sur un bouton Remplacer . Le gestionnaire du bouton crée un tableau qui mappe les noms aux espaces réservés, puis remplace chaque espace réservé par le nom attribué.
Vous n’avez pas besoin de produire un complément avec cette interface utilisateur pour tester le code. Vous pouvez utiliser l’outil Script Lab pour prototyper le code important. Utilisez l’instruction d’affectation suivante pour créer le tableau de mappage.
const jobMapping = [
{ job: "{Coordinator}", person: "Sally" },
{ job: "{Deputy}", person: "Bob" },
{ job: "{Manager}", person: "Kim" }
];
Le code suivant montre comment remplacer chaque espace réservé par son nom attribué si vous avez utilisé context.sync
des boucles internes.
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();
}
}
});
Dans le code précédent, il y a une boucle externe et une boucle interne. Chacun d’eux contient un context.sync
. D’après le tout premier extrait de code de cet article, vous voyez probablement que le context.sync
dans la boucle interne peut simplement être déplacé vers après la boucle interne. Mais cela laisserait toujours le code avec un context.sync
(deux d’entre eux en fait) dans la boucle externe. Le code suivant montre comment vous pouvez supprimer context.sync
des boucles. Nous aborderons le code plus loin.
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();
});
Notez que le code utilise le modèle de boucle fractionnée.
- La boucle externe de l’exemple précédent a été divisée en deux. (La deuxième boucle a une boucle interne, ce qui est attendu, car le code itération sur un ensemble de travaux (ou d’espaces réservés) et au sein de cet ensemble, il itération sur les plages correspondantes.)
- Il y a un
context.sync
après chaque boucle majeure, mais pascontext.sync
à l’intérieur d’une boucle. - La deuxième boucle principale itère au sein d’un tableau créé dans la première boucle.
Toutefois, le tableau créé dans la première boucle ne contient pas seulement un objet Office, comme la première boucle l’a fait dans la section Lecture des valeurs du document avec le modèle de boucle fractionnée. Cela est dû au fait que certaines des informations nécessaires pour traiter les Word objets Range ne se trouve pas dans les objets Range eux-mêmes, mais proviennent plutôt du jobMapping
tableau.
Par conséquent, les objets du tableau créé dans la première boucle sont des objets personnalisés qui ont deux propriétés. Le premier est un tableau de Word plages qui correspondent à un poste spécifique (autrement dit, une chaîne d’espace réservé) et le second est une chaîne qui fournit le nom de la personne affectée au travail. Cela facilite l’écriture et la lecture de la boucle finale, car toutes les informations nécessaires au traitement d’une plage donnée sont contenues dans le même objet personnalisé qui contient la plage. Le nom qui doit remplacer correlatedObject.rangesMatchingJob.items[j] est l’autre propriété du même objet : corrélerObject.personAssignedToJob.
Nous appelons cette variante du modèle de boucle fractionnée le modèle d’objets corrélés . L’idée générale est que la première boucle crée un tableau d’objets personnalisés. Chaque objet a une propriété dont la valeur est l’un des éléments d’un objet de collection Office (ou un tableau de ces éléments). L’objet personnalisé a d’autres propriétés, chacune fournissant les informations nécessaires pour traiter les objets Office dans la boucle finale. Consultez la section Autres exemples de ces modèles pour obtenir un lien vers un exemple où l’objet de corrélation personnalisé a plus de deux propriétés.
Une autre mise en garde : il suffit parfois de plusieurs boucles pour créer le tableau d’objets de corrélation personnalisés. Cela peut se produire si vous devez lire une propriété de chaque membre d’un objet de collection Office uniquement pour collecter des informations qui seront utilisées pour traiter un autre objet de collection. (Par exemple, votre code doit lire les titres de toutes les colonnes d’un tableau Excel, car votre complément va appliquer un format numérique aux cellules de certaines colonnes en fonction du titre de cette colonne.) Mais vous pouvez toujours conserver les context.sync
s entre les boucles, plutôt que dans une boucle. Pour obtenir un exemple, consultez la section Autres exemples de ces modèles .
Autres exemples de ces modèles
- Pour obtenir un exemple très simple pour Excel qui utilise
Array.forEach
des boucles, consultez la réponse acceptée à cette question Stack Overflow : Est-il possible de mettre en file d’attente plusieurs context.load avant context.sync ? - Pour obtenir un exemple simple de Word qui utilise
Array.forEach
des boucles et n’utiliseawait
async
/pas de syntaxe, consultez la réponse acceptée à cette question Stack Overflow : Itérer sur tous les paragraphes avec des contrôles de contenu avec l’API JavaScript Office. - Pour obtenir un exemple de Word écrit en TypeScript, consultez l’exemple Word Complément Angular2 Style Checker, en particulier le fichier word.document.service.ts. Il a un mélange de
for
boucles etArray.forEach
. - Pour obtenir un exemple de Word avancé, importez ce gist dans l’outil Script Lab. Pour connaître le contexte de l’utilisation du gist, consultez la réponse acceptée à la question Stack Overflow Document non synchronisé après le remplacement du texte. Cet exemple crée un type d’objet de corrélation personnalisé qui a trois propriétés. Il utilise un total de trois boucles pour construire le tableau d’objets corrélés, et deux autres boucles pour effectuer le traitement final. Il y a un mélange de
for
boucles etArray.forEach
. - Bien qu’il ne s’agisse pas strictement d’un exemple de boucle fractionnée ou de modèles d’objets corrélés, il existe un exemple Excel avancé qui montre comment convertir un ensemble de valeurs de cellule en d’autres devises avec un seul
context.sync
. Pour l’essayer, ouvrez l’outil Script Lab, puis recherchez et accédez à l’exemple Convertisseur de devises.
Quand ne devez-vous pas utiliser les modèles de cet article ?
Excel ne peut pas lire plus de 5 Mo de données dans un appel donné de context.sync
. Si cette limite est dépassée, une erreur est générée. (Pour plus d’informations, consultez la section « Compléments Excel » dans Limites de ressources et optimisation des performances pour les compléments Office .) Il est très rare que cette limite soit abordée, mais s’il y a une chance que cela se produise avec votre complément, votre code ne doit pas charger toutes les données dans une seule boucle et suivre la boucle avec un context.sync
. Toutefois, vous devez toujours éviter d’avoir un context.sync
dans chaque itération d’une boucle sur un objet de collection. Au lieu de cela, définissez des sous-ensembles des éléments de la collection et effectuez une boucle sur chaque sous-ensemble tour à tour, avec un context.sync
entre les boucles. Vous pouvez structurer cela avec une boucle externe qui itère sur les sous-ensembles et contient dans context.sync
chacune de ces itérations externes.