Partager via


É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.runexemple, , 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.syncentiè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.syncde 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 pas context.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 de context.sync pour remplir la text propriété de l’objet paragraph proxy. Au lieu de cela, il ajoute l’objet paragraph à 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 chaque paragraph élément. Cela est possible, car le context.sync qui est venu entre les deux boucles remplissait toutes les text 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.

  1. Remplacez la boucle par deux boucles.
  2. 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.
  3. Après la première boucle, appelez context.sync pour remplir les objets proxy avec toutes les propriétés chargées.
  4. 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 pas context.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.syncs 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

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.