You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/concepts/correlated-objects-pattern.md
+18-17Lines changed: 18 additions & 17 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,17 +2,17 @@
2
2
title: Avoid using the context.sync method in loops
3
3
description: Learn how to use the split loop and correlated objects patterns to avoid calling context.sync in a loop.
4
4
ms.topic: best-practice
5
-
ms.date: 08/18/2023
5
+
ms.date: 01/15/2025
6
6
ms.localizationpriority: medium
7
7
---
8
8
9
9
10
10
# Avoid using the context.sync method in loops
11
11
12
12
> [!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.
14
14
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 reador 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.
16
16
17
17
> [!NOTE]
18
18
> 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
34
34
> -`Array.reduceRight`
35
35
> -`Array.some`
36
36
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
+
37
40
## Writing to the document
38
41
39
42
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.
40
43
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
-
44
44
```javascript
45
45
awaitWord.run(asyncfunction (context) {
46
46
let startTime, endTime;
@@ -71,23 +71,24 @@ await Word.run(async function (context) {
71
71
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.
72
72
73
73
> [!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.
75
75
>
76
76
> - 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.
77
77
> - "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.
79
79
80
80
## Read values from the document with the split loop pattern
81
81
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.
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.
99
100
100
101
- 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.
102
103
- 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.
103
104
104
105
```javascript
@@ -125,12 +126,12 @@ The preceding example suggests the following procedure for turning a loop that c
125
126
126
127
1. Replace the loop with two loops.
127
128
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.
129
130
4. Follow the `context.sync` with a second loop to iterate over the array created in the first loop and read the loaded properties.
130
131
131
132
## Process objects in the document with the correlated objects pattern
132
133
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.
134
135
135
136
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.
136
137
@@ -146,7 +147,7 @@ The following code shows how you might replace each placeholder with its assigne
146
147
147
148
```javascript
148
149
Word.run(async (context) => {
149
-
150
+
// The context.sync calls in the loops will degrade performance.
150
151
for (let i =0; i <jobMapping.length; i++) {
151
152
let options =Word.SearchOptions.newObject(context);
152
153
options.matchWildCards=false;
@@ -164,7 +165,7 @@ Word.run(async (context) => {
164
165
});
165
166
```
166
167
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.
168
169
169
170
```javascript
170
171
Word.run(async (context) => {
@@ -206,7 +207,7 @@ Note the code uses the split loop pattern.
206
207
207
208
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.
208
209
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_.
210
211
211
212
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.
212
213
@@ -222,4 +223,4 @@ One further caveat: sometimes it takes more than one loop just to create the arr
222
223
223
224
## When should you *not* use the patterns in this article?
224
225
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