Skip to content

Commit 4cc3b21

Browse files
author
github-actions
committed
Merge branch 'main' into live
2 parents c94e213 + 4318e0e commit 4cc3b21

6 files changed

+81
-57
lines changed

docs/concepts/correlated-objects-pattern.md

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,17 @@
22
title: Avoid using the context.sync method in loops
33
description: Learn how to use the split loop and correlated objects patterns to avoid calling context.sync in a loop.
44
ms.topic: best-practice
5-
ms.date: 08/18/2023
5+
ms.date: 01/15/2025
66
ms.localizationpriority: medium
77
---
88

99

1010
# Avoid using the context.sync method in loops
1111

1212
> [!NOTE]
13-
> This article assumes that you're beyond the beginning stage of working with at least one of the four application-specific Office JavaScript APIs—for Excel, Word, OneNote, and Visio—that use a batch system to interact with the Office document. In particular, you should know what a call of `context.sync` does and you should know what a collection object is. If you're not at that stage, please start with [Understanding the Office JavaScript API](../develop/understanding-the-javascript-api-for-office.md) and the documentation linked to under "application-specific" in that article.
13+
> This article assumes that you're beyond the beginning stage of working with at least one of the four application-specific Office JavaScript APIs—for Excel, Word, OneNote, and Visio—that use a batch system to interact with the Office document. In particular, you should know what a call to `context.sync` does and you should know what a collection object is. If you're not at that stage, please start with [Understanding the Office JavaScript API](../develop/understanding-the-javascript-api-for-office.md) and the documentation linked to under "application-specific" in that article.
1414
15-
For some programming scenarios in Office Add-ins that use one of the application-specific API models (for Excel, Word, PowerPoint, OneNote, and Visio), your code needs to read, write, or process some property from every member of a collection object. For example, an Excel add-in that needs to get the values of every cell in a particular table column or a Word add-in that needs to highlight every instance of a string in the document. You need to iterate over the members in the `items` property of the collection object; but, for performance reasons, you need to avoid calling `context.sync` in every iteration of the loop. Every call of `context.sync` is a round trip from the add-in to the Office document. Repeated round trips hurt performance, especially if the add-in is running in Office on the web because the round trips go across the internet.
15+
Office Add-ins that use one of the [application-specific API models](../develop/application-specific-api-model.md) may have scenarios that require your code to read or write some property from every member of a collection object. For example, an Excel add-in that gets the values of every cell in a particular table column or a Word add-in that highlights every instance of a string in the document. You will need to iterate over the members in the `items` property of the collection object; but, for performance reasons, you should to avoid calling `context.sync` in every iteration of the loop. Every call of `context.sync` is a round trip from the add-in to the Office document. Repeated round trips hurt performance, especially if the add-in is running in Office on the web because the round trips go across the internet.
1616

1717
> [!NOTE]
1818
> All examples in this article use `for` loops but the practices described apply to any loop statement that can iterate through an array, including the following:
@@ -34,13 +34,13 @@ For some programming scenarios in Office Add-ins that use one of the application
3434
> - `Array.reduceRight`
3535
> - `Array.some`
3636
37+
> [!NOTE]
38+
> It's generally a good practice to put have a final `context.sync` just before the closing "}" character of the application `run` function (such as `Excel.run`, `Word.run`, etc.). This is because the `run` function makes a hidden call of `context.sync` as the last thing it does if, and only if, there are queued commands that haven't yet been synchronized. The fact that this call is hidden can be confusing, so we generally recommend that you add the explicit `context.sync`. However, given that this article is about minimizing calls of `context.sync`, it is actually more confusing to add an entirely unnecessary final `context.sync`. So, in this article, we leave it out when there are no unsynchronized commands at the end of the `run`.
39+
3740
## Writing to the document
3841

3942
In the simplest case, you are only writing to members of a collection object, not reading their properties. For example, the following code highlights in yellow every instance of "the" in a Word document.
4043

41-
> [!NOTE]
42-
> It's generally a good practice to put have a final `context.sync` just before the closing "}" character of the application `run` function (such as `Excel.run`, `Word.run`, etc.). This is because the `run` function makes a hidden call of `context.sync` as the last thing it does if, and only if, there are queued commands that haven't yet been synchronized. The fact that this call is hidden can be confusing, so we generally recommend that you add the explicit `context.sync`. However, given that this article is about minimizing calls of `context.sync`, it is actually more confusing to add an entirely unnecessary final `context.sync`. So, in this article, we leave it out when there are no unsynchronized commands at the end of the `run`.
43-
4444
```javascript
4545
await Word.run(async function (context) {
4646
let startTime, endTime;
@@ -71,23 +71,24 @@ await Word.run(async function (context) {
7171
The preceding code took 1 full second to complete in a document with 200 instances of "the" in Word on Windows. But when the `await context.sync();` line inside the loop is commented out and the same line just after the loop is uncommented, the operation took only a 1/10th of a second. In Word on the web (with Edge as the browser), it took 3 full seconds with the synchronization inside the loop and only 6/10ths of a second with the synchronization after the loop, about five times faster. In a document with 2000 instances of "the", it took (in Word on the web) 80 seconds with the synchronization inside the loop and only 4 seconds with the synchronization after the loop, about 20 times faster.
7272

7373
> [!NOTE]
74-
> It's worth asking whether the synchronize-inside-the-loop version would execute faster if the synchronizations ran concurrently, which could be done by simply removing the `await` keyword from the front of the `context.sync()`. This would cause the runtime to initiate the synchronization and then immediately start the next iteration of the loop without waiting for the synchronization to complete. However, this isn't as good a solution as moving the `context.sync` out of the loop entirely for these reasons.
74+
> It's worth asking whether the synchronize-inside-the-loop version would execute faster if the synchronizations ran concurrently, which could be done by simply removing the `await` keyword from the front of the `context.sync()`. This would cause the runtime to initiate the synchronization and then immediately start the next iteration of the loop without waiting for the synchronization to complete. However, this isn't as good a solution as moving the `context.sync` out of the loop entirely for the following reasons.
7575
>
7676
> - Just as the commands in a synchronization batch job are queued, the batch jobs themselves are queued in Office, but Office supports no more than 50 batch jobs in the queue. Any more triggers errors. So, if there are more than 50 iterations in a loop, there's a chance that the queue size is exceeded. The greater the number of iterations, the greater the chance of this happening.
7777
> - "Concurrently" doesn't mean simultaneously. It would still take longer to execute multiple synchronization operations than to execute one.
78-
> - Concurrent operations aren't guaranteed to complete in the same order in which they started. In the preceding example, it doesn't matter what order the word "the" gets highlighted, but there are scenarios where it's important that the items in the collection be processed in order.
78+
> - Concurrent operations aren't guaranteed to complete in the same order in which they started. In the preceding example, it doesn't matter what order the word "the" gets highlighted, but there are scenarios where it's important that the items in the collection be processed in order.
7979
8080
## Read values from the document with the split loop pattern
8181

82-
Avoiding `context.sync`s inside a loop becomes more challenging when the code must *read* a property of the collection items as it processes each one. Suppose your code needs to iterate all the content controls in a Word document and log the text of the first paragraph associated with each control. Your programming instincts might lead you to loop over the controls, load the `text` property of each (first) paragraph, call `context.sync` to populate the proxy paragraph object with the text from the document, and then log it. The following is an example.
82+
Avoiding `context.sync` inside a loop becomes more challenging when the code must *read* a property of the collection items as it processes each one. Suppose your code needs to iterate all the content controls in a Word document and log the text of the first paragraph associated with each control. Your programming instincts might lead you to loop over the controls, load the `text` property of each (first) paragraph, call `context.sync` to populate the proxy paragraph object with the text from the document, and then log it. The following is an example.
8383

8484
```javascript
8585
Word.run(async (context) => {
8686
const contentControls = context.document.contentControls.load('items');
8787
await context.sync();
8888

8989
for (let i = 0; i < contentControls.items.length; i++) {
90-
const paragraph = contentControls.items[i].getRange('Whole').paragraphs.getFirst();
90+
// The sync statement in this loop will degrade performance.
91+
const paragraph = contentControls.items[i].getRange('Whole').paragraphs.getFirst();
9192
paragraph.load('text');
9293
await context.sync();
9394
console.log(paragraph.text);
@@ -98,7 +99,7 @@ Word.run(async (context) => {
9899
In this scenario, to avoid having a `context.sync` in a loop, you should use a pattern we call the **split loop** pattern. Let's see a concrete example of the pattern before we get to a formal description of it. Here's how the split loop pattern can be applied to the preceding code snippet. Note the following about this code.
99100

100101
- There are now two loops and the `context.sync` comes between them, so there's no `context.sync` inside either loop.
101-
- The first loop iterates through the items in the collection object and loads the `text` property just as the original loop did, but the first loop cannot log the paragraph text because it no longer contains a `context.sync` to populate the `text` property of the `paragraph` proxy object. Instead, it adds the `paragraph` object to an array.
102+
- The first loop iterates through the items in the collection object and loads the `text` property, just as the original loop did, but the first loop cannot log the paragraph text because it no longer contains a `context.sync` to populate the `text` property of the `paragraph` proxy object. Instead, it adds the `paragraph` object to an array.
102103
- The second loop iterates through the array that was created by the first loop, and logs the `text` of each `paragraph` item. This is possible because the `context.sync` that came between the two loops populated all the `text` properties.
103104

104105
```javascript
@@ -125,12 +126,12 @@ The preceding example suggests the following procedure for turning a loop that c
125126

126127
1. Replace the loop with two loops.
127128
2. Create a first loop to iterate over the collection and add each item to an array while also loading any property of the item that your code needs to read.
128-
3. Following the first loop, call `context.sync` to populate the proxy objects with any loaded properties.
129+
3. Follow the first loop with `context.sync` to populate the proxy objects with any loaded properties.
129130
4. Follow the `context.sync` with a second loop to iterate over the array created in the first loop and read the loaded properties.
130131

131132
## Process objects in the document with the correlated objects pattern
132133

133-
Let's consider a more complex scenario where processing the items in the collection requires data that isn't in the items themselves. The scenario envisions a Word add-in that operates on documents created from a template with some boilerplate text. Scattered in the text are one or more instances of the following placeholder strings: "{Coordinator}", "{Deputy}", and "{Manager}". The add-in replaces each placeholder with some person's name. The UI of the add-in isn't important to this article. For example, it could have a task pane with three text boxes, each labeled with one of the placeholders. The user enters a name in each text box and then presses a **Replace** button. The handler for the button creates an array that maps the names to the placeholders, and then replaces each placeholder with the assigned name.
134+
Let's consider a more complex scenario where processing the items in the collection requires data that isn't in the items themselves. The scenario envisions a Word add-in that operates on documents created from a template with some boilerplate text. Scattered in the text are one or more instances of the following placeholder strings: "{Coordinator}", "{Deputy}", and "{Manager}". The add-in replaces each placeholder with some person's name. While the UI of the add-in isn't important to this article, the add-in could have a task pane with three text boxes, each labeled with one of the placeholders. The user enters a name in each text box and then presses a **Replace** button. The handler for the button creates an array that maps the names to the placeholders, and then replaces each placeholder with the assigned name.
134135

135136
You don't need to actually produce an add-in with this UI to experiment with the code. You can use the [Script Lab tool](../overview/explore-with-script-lab.md) to prototype the important code. Use the following assignment statement to create the mapping array.
136137

@@ -146,7 +147,7 @@ The following code shows how you might replace each placeholder with its assigne
146147

147148
```javascript
148149
Word.run(async (context) => {
149-
150+
// The context.sync calls in the loops will degrade performance.
150151
for (let i = 0; i < jobMapping.length; i++) {
151152
let options = Word.SearchOptions.newObject(context);
152153
options.matchWildCards = false;
@@ -164,7 +165,7 @@ Word.run(async (context) => {
164165
});
165166
```
166167

167-
In the preceding code, there's an outer and an inner loop. Each of them contains a `context.sync`. Based on the very first code snippet in this article, you probably see that the `context.sync` in the inner loop can simply be moved to after the inner loop. But that would still leave the code with a `context.sync` (two of them actually) in the outer loop. The following code shows how you can remove `context.sync` from the loops. We discuss the code later.
168+
In the preceding code, there's an outer and an inner loop. Each of them contains a `context.sync` call. Based on the first code snippet in this article, you probably see that the `context.sync` in the inner loop can simply be moved after the inner loop. But that would still leave the code with a `context.sync` (two of them actually) in the outer loop. The following code shows how you can remove `context.sync` from the loops. We discuss the code later.
168169

169170
```javascript
170171
Word.run(async (context) => {
@@ -206,7 +207,7 @@ Note the code uses the split loop pattern.
206207

207208
But the array created in the first loop does *not* contain only an Office object as the first loop did in the section [Reading values from the document with the split loop pattern](#read-values-from-the-document-with-the-split-loop-pattern). This is because some of the information needed to process the Word Range objects is not in the Range objects themselves but instead comes from the `jobMapping` array.
208209

209-
So, the objects in the array created in the first loop are custom objects that have two properties. The first is an array of Word Ranges that match a specific job title (that is, a placeholder string) and the second is a string that provides the name of the person assigned to the job. This makes the final loop easy to write and easy to read because all of the information needed to process a given range is contained in the same custom object that contains the range. The name that should replace _**correlatedObject**.rangesMatchingJob.items[j]_ is the other property of the same object: _**correlatedObject**.personAssignedToJob_.
210+
So, the objects in the array created in the first loop are custom objects that have two properties. The first is an array of Word ranges that match a specific job title (that is, a placeholder string) and the second is a string that provides the name of the person assigned to the job. This makes the final loop easy to write and easy to read because all of the information needed to process a given range is contained in the same custom object that contains the range. The name that should replace _**correlatedObject**.rangesMatchingJob.items[j]_ is the other property of the same object: _**correlatedObject**.personAssignedToJob_.
210211

211212
We call this variation of the split loop pattern the **correlated objects** pattern. The general idea is that the first loop creates an array of custom objects. Each object has a property whose value is one of the items in an Office collection object (or an array of such items). The custom object has other properties, each of which provides information needed to process the Office objects in the final loop. See the section [Other examples of these patterns](#other-examples-of-these-patterns) for a link to an example where the custom correlating object has more than two properties.
212213

@@ -222,4 +223,4 @@ One further caveat: sometimes it takes more than one loop just to create the arr
222223

223224
## When should you *not* use the patterns in this article?
224225

225-
Excel can't read more than 5 MB of data in a given call of `context.sync`. If this limit is exceeded, an error is thrown. (See the "Excel add-ins section" of [Resource limits and performance optimization for Office Add-ins](resource-limits-and-performance-optimization.md#excel-add-ins) for more information.) It's very rare that this limit is approached, but if there's a chance that this will happen with your add-in, then your code should *not* load all the data in a single loop and follow the loop with a `context.sync`. But you still should avoid having a `context.sync` in every iteration of a loop over a collection object. Instead, define subsets of the items in the collection and loop over each subset in turn, with a `context.sync` between the loops. You could structure this with an outer loop that iterates over the subsets and contains the `context.sync` in each of these outer iterations.
226+
Excel can't read more than 5MB of data in a given call of `context.sync`. If this limit is exceeded, an error is thrown. (See the "Excel add-ins section" of [Resource limits and performance optimization for Office Add-ins](resource-limits-and-performance-optimization.md#excel-add-ins) for more information.) It's very rare that this limit is approached, but if there's a chance that this will happen with your add-in, then your code should *not* load all the data in a single loop and follow the loop with a `context.sync`. But you still should avoid having a `context.sync` in every iteration of a loop over a collection object. Instead, define subsets of the items in the collection and loop over each subset in turn, with a `context.sync` between the loops. You could structure this with an outer loop that iterates over the subsets and contains the `context.sync` in each of these outer iterations.

0 commit comments

Comments
 (0)