Vermeiden der Verwendung der context.sync-Methode in Schleifen

Hinweis

In diesem Artikel wird davon ausgegangen, dass Sie sich in der Anfangsphase der Arbeit mit mindestens einer der vier anwendungsspezifischen Office-JavaScript-APIs für Excel, Word, OneNote und Visio befinden, die ein Batchsystem für die Interaktion mit dem Office-Dokument verwenden. Insbesondere sollten Sie wissen, was ein Aufruf bewirkt context.sync , und Sie sollten wissen, was ein Sammlungsobjekt ist. Wenn Sie sich noch nicht in diesem Stadium befinden, beginnen Sie mit den Grundlagen der Office JavaScript-API und der Dokumentation, die in diesem Artikel unter "anwendungsspezifisch" verlinkt ist.

Office-Add-Ins, die eines der anwendungsspezifischen API-Modelle verwenden, können Szenarien aufweisen, in denen Ihr Code eine Eigenschaft von jedem Element eines Auflistungsobjekts lesen oder schreiben muss. Beispielsweise ein Excel-Add-In, das die Werte jeder Zelle in einer bestimmten Tabellenspalte abruft, oder ein Word-Add-In, das jede Instance einer Zeichenfolge im Dokument hervorhebt. Sie müssen über die Member in der items Eigenschaft des Auflistungsobjekts iterieren. Aus Leistungsgründen sollten Sie jedoch vermeiden, dass jede Iteration der Schleife aufgerufen context.sync wird. Jeder Aufruf von context.sync ist ein Roundtrip vom Add-In zum Office-Dokument. Wiederholte Roundtrips beeinträchtigen die Leistung, insbesondere wenn das Add-In in Office im Web ausgeführt wird, da die Roundtrips über das Internet führen.

Hinweis

In allen Beispielen in diesem Artikel werden Schleifen verwendet for , aber die beschriebenen Praktiken gelten für jede Schleifenanweisung, die ein Array durchlaufen kann, einschließlich der folgenden:

  • for
  • for of
  • while
  • do while

Sie gelten auch für jede Arraymethode, an die eine Funktion übergeben und auf die Elemente im Array angewendet wird, einschließlich der folgenden:

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

Hinweis

Es ist im Allgemeinen eine bewährte Praxis, ein Finale context.sync direkt vor dem abschließenden "}"-Zeichen der Anwendungsfunktion run (z. B Excel.run. , Word.runusw.) einzufügen. Dies liegt daran, dass die run Funktion als letztes einen versteckten Aufruf context.sync durchführt, wenn und nur dann Befehle in der Warteschlange vorhanden sind, die noch nicht synchronisiert wurden. Die Tatsache, dass dieser Aufruf ausgeblendet ist, kann verwirrend sein. Daher empfehlen wir im Allgemeinen, den expliziten context.sync. Da es in diesem Artikel jedoch um die Minimierung von Aufrufen von context.syncgeht, ist es tatsächlich verwirrender, ein völlig unnötiges Finale context.synchinzuzufügen. In diesem Artikel lassen wir es also weg, wenn am Ende des run.

In das Dokument schreiben

Im einfachsten Fall schreiben Sie nur in Member eines Collection-Objekts, nicht aber in deren Eigenschaften. Der folgende Code hebt z. B. jede Instance von "the" in einem Word-Dokument gelb hervor.

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.");
})

Der vorherige Code benötigte 1 volle Sekunde, um in einem Dokument mit 200 Instanzen von "the" in Word unter Windows abgeschlossen zu werden. Wenn jedoch die await context.sync(); Zeile innerhalb der Schleife auskommentiert ist und dieselbe Zeile unmittelbar nach der Schleife auskommentiert wird, dauert der Vorgang nur 1/10 Sekunde. In Word im Web (mit Edge als Browser) dauerte es mit der Synchronisierung innerhalb der Schleife 3 volle Sekunden und mit der Synchronisierung nach der Schleife nur 6/10 Sekunden, etwa fünfmal schneller. In einem Dokument mit 2000 Instanzen von "the" dauerte es (in Word im Web) 80 Sekunden mit der Synchronisation innerhalb der Schleife und nur 4 Sekunden mit der Synchronisation nach der Schleife, etwa 20-mal schneller.

Hinweis

Es lohnt sich zu fragen, ob die Synchronize-Inside-the-Loop-Version schneller ausgeführt würde, wenn die Synchronisationen gleichzeitig ausgeführt würden, was durch einfaches Entfernen des await Schlüsselworts (Keyword) von der Vorderseite context.sync()des . Dies würde dazu führen, dass die Laufzeit die Synchronisierung initiiert und dann sofort die nächste Iteration der Schleife startet, ohne auf den Abschluss der Synchronisierung zu warten. Dies ist jedoch aus den folgenden Gründen keine so gute Lösung wie das context.sync vollständige Herausnehmen der Schleife.

  • So wie die Befehle in einem Synchronisierungs-Batchauftrag in die Warteschlange eingereiht werden, werden die Batchaufträge selbst in Office in die Warteschlange eingereiht, aber Office unterstützt nicht mehr als 50 Batchaufträge in der Warteschlange. Alle weiteren löst Fehler aus. Wenn also mehr als 50 Iterationen in einer Schleife enthalten sind, besteht die Möglichkeit, dass die Warteschlangengröße überschritten wird. Je größer die Anzahl der Iterationen, desto größer ist die Wahrscheinlichkeit, dass dies geschieht.
  • "Gleichzeitig" bedeutet nicht gleichzeitig. Das Ausführen mehrerer Synchronisierungsvorgänge würde immer noch länger dauern als das Ausführen eines einzigen.
  • Es ist nicht garantiert, dass gleichzeitige Vorgänge in der gleichen Reihenfolge abgeschlossen werden, in der sie begonnen haben. Im vorherigen Beispiel spielt es keine Rolle, in welcher Reihenfolge das Wort "the" hervorgehoben wird, aber es gibt Szenarien, in denen es wichtig ist, dass die Elemente in der Sammlung in der richtigen Reihenfolge verarbeitet werden.

Lesen von Werten aus dem Dokument mit dem geteilten Schleifenmuster

Das Vermeiden context.sync innerhalb einer Schleife wird schwieriger, wenn der Code eine Eigenschaft der Sammlungselemente lesen muss, während er jedes einzelne verarbeitet. Angenommen, Ihr Code muss alle Inhaltssteuerelemente in einem Word-Dokument durchlaufen und den Text des ersten Absatzes protokollieren, der jedem Steuerelement zugeordnet ist. Ihr Programmierinstinkt kann dazu führen, dass Sie die Steuerelemente durchlaufen, die text Eigenschaft jedes (ersten) Absatzes laden, context.sync das Proxyabsatzobjekt mit dem Text aus dem Dokument füllen und es dann protokollieren. Es folgt ein Beispiel.

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);
    }
});

Um in diesem Szenario eine context.sync Schleife zu vermeiden, sollten Sie ein Muster verwenden, das wir als geteiltes Schleifenmuster bezeichnen. Sehen wir uns ein konkretes Beispiel für das Muster an, bevor wir zu einer formalen Beschreibung kommen. Hier erfahren Sie, wie das geteilte Schleifenmuster auf den vorhergehenden Codeausschnitt angewendet werden kann. Beachten Sie die folgenden Aspekte in diesem Code.

  • Es gibt jetzt zwei Schleifen und die context.sync kommen dazwischen, also gibt es in context.sync keiner der beiden Schleifen eine Innenschleife.
  • Die erste Schleife durchläuft die Elemente im Sammlungsobjekt und lädt die text Eigenschaft, genau wie die ursprüngliche Schleife, aber die erste Schleife kann den Absatztext nicht protokollieren, da sie kein zum context.sync Auffüllen der Eigenschaft des Proxyobjekts textparagraph mehr enthält. Stattdessen fügt er das paragraph Objekt einem Array hinzu.
  • Die zweite Schleife durchläuft das Array, das von der ersten Schleife erstellt wurde, und protokolliert die text der einzelnen paragraph Elemente. Dies ist möglich, da das context.sync zwischen den beiden Schleifen liegende alle text Eigenschaften auffüllte.
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);
    }
});

Im vorstehenden Beispiel wird das folgende Verfahren zum Umwandeln einer Schleife, die eine context.sync in das geteilte Schleifenmuster enthält, vorgeschlagen.

  1. Ersetzen Sie die Schlaufe durch zwei Schlaufen.
  2. Erstellen Sie eine erste Schleife, um die Auflistung zu durchlaufen und jedes Element einem Array hinzuzufügen, während gleichzeitig alle Eigenschaften des Elements geladen werden, die der Code lesen muss.
  3. Folgen Sie der ersten Schleife mit context.sync , um die Proxyobjekte mit allen geladenen Eigenschaften aufzufüllen.
  4. context.sync Führen Sie eine zweite Schleife aus, um das in der ersten Schleife erstellte Array zu durchlaufen und die geladenen Eigenschaften zu lesen.

Verarbeiten von Objekten im Dokument mit dem Muster für korrelierte Objekte

Betrachten wir ein komplexeres Szenario, in dem für die Verarbeitung der Elemente in der Sammlung Daten erforderlich sind, die sich nicht in den Elementen selbst befinden. Das Szenario stellt sich ein Word-Add-In vor, das mit Dokumenten arbeitet, die aus einer Vorlage mit einigen Textbausteinen erstellt wurden. Im Text sind eine oder mehrere Instanzen der folgenden Platzhalterzeichenfolgen verstreut: "{Coordinator}", "{Deputy}" und "{Manager}". Das Add-In ersetzt jeden Platzhalter durch den Namen einer Person. Die Benutzeroberfläche des Add-Ins ist für diesen Artikel zwar nicht wichtig, das Add-In könnte jedoch einen Aufgabenbereich mit drei Textfeldern haben, die jeweils mit einem der Platzhalter beschriftet sind. Der Benutzer gibt in jedes Textfeld einen Namen ein und drückt dann auf die Schaltfläche "Ersetzen ". Der Handler für die Schaltfläche erstellt ein Array, das die Namen den Platzhaltern zuordnet, und ersetzt dann jeden Platzhalter durch den zugewiesenen Namen.

Sie können das Tool Script Lab verwenden, um den hier gezeigten Codeausschnitten zu folgen. In Word können Sie das Beispiel "Korreliertes Objektmuster" laden oder diesen Beispielcode aus dem GitHub-Repository importieren.

Die folgende Zuweisungsanweisung erstellt das Zuordnungsarray zwischen Platzhalter und zugewiesenen Namen.

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

Der folgende Code zeigt, wie Sie jeden Platzhalter durch den zugewiesenen Namen ersetzen können, wenn Sie innerhalb von Schleifen verwendet context.sync haben. Dies entspricht der replacePlaceholdersSlow Funktion im Beispiel.

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();
      }
    }
});

Im vorherigen Code gibt es eine äußere und eine innere Schleife. Jede von ihnen enthält einen context.sync Aufruf. Basierend auf dem ersten Codeausschnitt in diesem Artikel sehen Sie wahrscheinlich, dass der context.sync in der inneren Schleife einfach nach der inneren Schleife verschoben werden kann. Aber das würde den Code immer noch mit einem context.sync (eigentlich zwei davon) in der äußeren Schleife belassen. Der folgende Code zeigt, wie Sie aus den Schleifen entfernen context.sync können. Sie entspricht der replacePlaceholders Funktion im Beispiel. Wir besprechen den Code später.

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();
});

Beachten Sie, dass der Code das geteilte Schleifenmuster verwendet.

  • Die äußere Schleife aus dem vorherigen Beispiel wurde in zwei Teile geteilt. (Die zweite Schleife verfügt über eine innere Schleife, die erwartet wird, da der Code über eine Reihe von Aufträgen (oder Platzhaltern) und innerhalb dieses Satzes über die übereinstimmenden Bereiche iteriert.)
  • Es gibt eine context.sync nach jeder Hauptschleife, aber keine context.sync innerhalb einer Schleife.
  • Die zweite Hauptschleife durchläuft ein Array, das in der ersten Schleife erstellt wird.

Das in der ersten Schleife erstellte Array enthält jedoch nicht nur ein Office-Objekt, wie dies in der ersten Schleife im Abschnitt Lesen von Werten aus dem Dokument mit dem geteilten Schleifenmuster der Fall war. Dies liegt daran, dass einige der Informationen, die zum Verarbeiten der Word Range-Objekte erforderlich sind, sich nicht in den Range-Objekten selbst befinden, sondern aus dem jobMapping Array stammen.

Die Objekte in dem in der ersten Schleife erstellten Array sind also benutzerdefinierte Objekte mit zwei Eigenschaften. Die erste ist ein Array von Word-Bereichen, die einer bestimmten Stellenbezeichnung entsprechen (d. h. eine Platzhalterzeichenfolge), und die zweite ist eine Zeichenfolge, die den Namen der Person enthält, die der Stelle zugewiesen ist. Dadurch ist die letzte Schleife einfach zu schreiben und zu lesen, da alle Informationen, die zum Verarbeiten eines bestimmten Bereichs erforderlich sind, in demselben benutzerdefinierten Objekt enthalten sind, das den Bereich enthält. Der Name, der correlatedObject.rangesMatchingJob.items[j] ersetzen soll, ist die andere Eigenschaft desselben Objekts: correlatedObject.personAssignedToJob.

Wir nennen diese Variation des geteilten Schleifenmusters das korrelierte Objektmuster. Die allgemeine Idee ist, dass die erste Schleife ein Array benutzerdefinierter Objekte erstellt. Jedes Objekt verfügt über eine Eigenschaft, deren Wert eines der Elemente in einem Office-Sammlungsobjekt (oder einem Array solcher Elemente) ist. Das benutzerdefinierte Objekt verfügt über weitere Eigenschaften, von denen jede Informationen bereitstellt, die für die Verarbeitung der Office-Objekte in der letzten Schleife erforderlich sind. Siehe den Abschnitt Andere Beispiele für diese Muster für einen Link zu einem Beispiel, in dem das benutzerdefinierte korrelierende Objekt mehr als zwei Eigenschaften hat.

Eine weitere Einschränkung: Manchmal dauert es mehr als eine Schleife, nur um das Array benutzerdefinierter korrelierender Objekte zu erstellen. Dies kann vorkommen, wenn Sie eine Eigenschaft jedes Mitglieds eines Office-Sammlungsobjekts lesen müssen, nur um Informationen zu sammeln, die zum Verarbeiten eines anderen Sammlungsobjekts verwendet werden. (Ihr Code muss beispielsweise die Titel aller Spalten in einer Excel-Tabelle lesen, da Ihr Add-In ein Zahlenformat auf die Zellen einiger Spalten anwenden wird, das auf dem Titel dieser Spalte basiert.) Aber Sie können das context.syncs immer zwischen den Schleifen halten, anstatt in einer Schleife. Ein Beispiel finden Sie im Abschnitt Andere Beispiele für diese Muster .

Weitere Beispiele für diese Muster

  • Ein sehr einfaches Beispiel für Excel, das Schleifen verwendet Array.forEach , finden Sie in der akzeptierten Antwort auf diese Stack Overflow-Frage: Ist es möglich, mehr als eine context.load vor context.sync in die Warteschlange zu stellen?
  • Ein einfaches Beispiel für Word, das Schleifen und keine Syntax verwendet Array.forEachawaitasync/, finden Sie in der akzeptierten Antwort auf diese Stack Overflow-Frage: Iterieren über alle Absätze mit Inhaltssteuerelementen mit Office JavaScript-API.
  • Importieren Sie für ein erweitertes Word-Beispiel diesen Kern in das Script Lab-Tool. Der Kontext für die Verwendung des Kerninhalts finden Sie in der akzeptierten Antwort auf die Stack Overflow-Frage "Dokument nach dem Ersetzen des Textes nicht synchronisiert". In diesem Beispiel wird ein benutzerdefinierter korrelierender Objekttyp erstellt, der über drei Eigenschaften verfügt. Es verwendet insgesamt drei Schleifen, um das Array korrelierter Objekte zu erstellen, und zwei weitere Schleifen für die endgültige Verarbeitung. Es gibt eine Mischung aus forArray.forEach und Schleifen.
  • Obwohl es sich nicht unbedingt um ein Beispiel für die geteilte Schleife oder korrelierte Objektmuster handelt, gibt es ein erweitertes Excel-Beispiel, das zeigt, wie eine Reihe von Zellwerten mit nur einem einzigen context.syncin andere Währungen konvertiert werden kann. Um es auszuprobieren, öffnen Sie das Tool Script Lab, suchen Sie nach dem Beispiel "Währungsrechner", und navigieren Sie zum Beispiel.

Wann sollten Sie die Muster in diesem Artikel nicht verwenden?

Excel im Web kann nicht mehr als 5 MB Daten in einem bestimmten Aufruf von context.synclesen. Wenn dieser Grenzwert überschritten wird, wird ein Fehler ausgelöst. (Weitere Informationen finden Sie unter Ressourcenlimits und Leistungsoptimierung für Office-Add-Ins im Abschnitt "Excel-Add-Ins".) Es kommt sehr selten vor, dass dieser Grenzwert erreicht wird, aber wenn die Möglichkeit besteht, dass dies auch bei Ihrem Add-In der Fall ist, sollte Ihr Code nicht alle Daten in einer einzigen Schleife laden und der Schleife eine context.syncfolgen. Sie sollten es dennoch vermeiden, in jeder Iteration einer Schleife über einem Sammlungsobjekt eine context.sync zu haben. Definieren Sie stattdessen Teilmengen der Elemente in der Auflistung, und durchlaufen Sie jede Teilmenge nacheinander in einer Schleife mit einer context.sync Schleife zwischen den Schleifen. Sie können dies mit einer äußeren Schleife strukturieren, die über die Teilmengen iteriert und die context.sync in jeder dieser äußeren Iterationen enthält.