Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Nota:
En este artículo se presupone que ha superado la fase inicial de trabajar con al menos una de las cuatro API de JavaScript de Office específicas de la aplicación (para Excel, Word, OneNote y Visio) que usan un sistema por lotes para interactuar con el documento de Office. En particular, debe saber qué hace una llamada a context.sync y debe saber qué es un objeto de colección. Si aún no está en esa fase, empiece por Comprender la API de JavaScript de Office y la documentación vinculada en "Específico de la aplicación" en ese artículo.
Los complementos de Office que usan uno de los modelos de API específicos de la aplicación pueden tener escenarios que requieren que el código lea o escriba alguna propiedad de cada miembro de un objeto de colección. Por ejemplo, un complemento de Excel que obtiene los valores de cada celda de una columna de tabla determinada o un complemento de Word que resalta cada instancia de una cadena en el documento. Tendrá que iterar sobre los miembros de la items propiedad del objeto de colección; pero, por razones de rendimiento, debe evitar llamar context.sync a cada iteración del bucle. Cada llamada es un recorrido de context.sync ida y vuelta desde el complemento al documento de Office. Los viajes de ida y vuelta repetidos perjudican el rendimiento, especialmente si el complemento se ejecuta en Office en la Web porque estos viajes de ida y vuelta se realizan a través de Internet.
Nota:
Todos los ejemplos de este artículo usan for bucles, pero las prácticas descritas se aplican a cualquier instrucción de bucle que pueda iterar a través de una matriz, incluidas las siguientes:
forfor ofwhiledo while
También se aplican a cualquier método de matriz al que se pasa una función y se aplica a los elementos de la matriz, incluidos los siguientes:
Array.everyArray.forEachArray.filterArray.findArray.findIndexArray.mapArray.reduceArray.reduceRightArray.some
Nota:
Por lo general, es una buena práctica poner un final context.sync justo antes del carácter de cierre "}" de la función de la aplicación run (como Excel.run, Word.run, etc.). Esto se debe a que la run función hace una llamada oculta como context.sync lo último que hace si, y solo si, hay comandos en cola que aún no se han sincronizado. El hecho de que esta llamada esté oculta puede crear confusión, por lo que generalmente recomendamos que agregue el explícito context.sync. Sin embargo, dado que este artículo trata sobre minimizar las llamadas de context.sync, en realidad es más confuso agregar un final context.synccompletamente innecesario . Entonces, en este artículo, lo dejamos fuera cuando no hay comandos no sincronizados al final del runarchivo .
Escribir en el documento
En el caso más sencillo, solo está escribiendo en miembros de un objeto de colección, no leyendo sus propiedades. Por ejemplo, el código siguiente resalta en amarillo todas las instancias de "the" en un documento de Word.
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.");
})
El código anterior tardó 1 segundo completo en completarse en un documento con 200 instancias de "the" en Word en Windows. Pero cuando se comenta la await context.sync(); línea dentro del bucle y se descomenta la misma línea justo después del bucle, la operación tomó solo 1/10 de segundo. En Word en la web (con Edge como explorador), tardaba 3 segundos completos con la sincronización dentro del bucle y solo 6/10 de segundo con la sincronización después del bucle, unas cinco veces más rápido. En un documento con 2000 instancias de "the", tardó (en Word en la Web) 80 segundos con la sincronización dentro del bucle y solo 4 segundos con la sincronización después del bucle, unas 20 veces más rápido.
Nota:
Vale la pena preguntarse si la versión de sincronización dentro del bucle se ejecutaría más rápido si las sincronizaciones se ejecutaran simultáneamente, lo que podría hacerse simplemente quitando la await palabra clave del frente del context.sync()archivo . Esto haría que el tiempo de ejecución iniciara la sincronización e iniciara inmediatamente la siguiente iteración del bucle sin esperar a que se completara la sincronización. Sin embargo, esta no es una solución tan buena como sacar el context.sync circuito por completo por las siguientes razones.
- Al igual que los comandos de un trabajo por lotes de sincronización se ponen en cola, los propios trabajos por lotes se ponen en cola en Office, pero Office no admite más de 50 trabajos por lotes en la cola. Más desencadena errores. Por lo tanto, si hay más de 50 iteraciones en un bucle, existe la posibilidad de que se supere el tamaño de la cola. Cuanto mayor sea el número de iteraciones, mayor será la posibilidad de que esto suceda.
- "Al mismo tiempo" no significa simultáneamente. Todavía tardaría más en ejecutar varias operaciones de sincronización que en ejecutar una.
- No se garantiza que las operaciones simultáneas se completen en el mismo orden en que comenzaron. En el ejemplo anterior, no importa el orden en que se resalte la palabra "the", pero hay escenarios en los que es importante que los elementos de la colección se procesen en orden.
Leer valores del documento con el patrón de bucle dividido
Evitar context.sync dentro de un bucle se vuelve más difícil cuando el código debe leer una propiedad de los elementos de colección a medida que procesa cada uno. Suponga que el código necesita iterar todos los controles de contenido de un documento de Word y registrar el texto del primer párrafo asociado a cada control. Sus instintos de programación pueden llevarlo a recorrer los controles, cargar la text propiedad de cada (primer) párrafo, llamar context.sync para rellenar el objeto de párrafo proxy con el texto del documento y, a continuación, registrarlo. A continuación se muestra un ejemplo.
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);
}
});
En este escenario, para evitar tener un context.sync bucle en un bucle, debe usar un patrón que llamamos patrón de bucle dividido . Veamos un ejemplo concreto del patrón antes de llegar a una descripción formal del mismo. Aquí se muestra cómo se puede aplicar el patrón de bucle dividido al fragmento de código anterior. Tenga en cuenta lo siguiente sobre este código.
- Ahora hay dos bucles y se
context.syncinterponen entre ellos, por lo que nocontext.synchay dentro de ninguno de los bucles. - El primer bucle itera a través de los elementos del objeto de colección y carga la
textpropiedad, tal como lo hizo el bucle original, pero el primer bucle no puede registrar el texto del párrafo porque ya no contiene acontext.syncpara rellenar latextpropiedad delparagraphobjeto proxy. En lugar de eso, agrega elparagraphobjeto a una matriz. - El segundo bucle itera a través de la matriz creada por el primer bucle y registra la
textinformación de cadaparagraphelemento. Esto es posible porque locontext.syncque vino entre los dos bucles llenó todas lastextpropiedades.
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);
}
});
En el ejemplo anterior se sugiere el siguiente procedimiento para convertir un bucle que contiene a context.sync en el patrón de bucle dividido.
- Reemplace el bucle por dos bucles.
- Cree un primer bucle para iterar sobre la colección y agregar cada elemento a una matriz mientras carga cualquier propiedad del elemento que su código necesita leer.
- Siga el primer bucle para
context.syncrellenar los objetos proxy con las propiedades cargadas. - Siga con
context.syncun segundo bucle para iterar sobre la matriz creada en el primer bucle y leer las propiedades cargadas.
Procesar objetos del documento con el patrón de objetos correlacionados
Consideremos un escenario más complejo en el que el procesamiento de los elementos de la colección requiere datos que no están en los propios elementos. El escenario prevé un complemento de Word que funciona en documentos creados a partir de una plantilla con texto reutilizable. Dispersas en el texto hay una o más instancias de las siguientes cadenas de marcador de posición: "{Coordinator}", "{Deputy}" y "{Manager}". El complemento reemplaza cada marcador de posición por el nombre de alguien. Aunque la interfaz de usuario del complemento no es importante para este artículo, el complemento podría tener un panel de tareas con tres cuadros de texto, cada uno etiquetado con uno de los marcadores de posición. El usuario escribe un nombre en cada cuadro de texto y después presiona el botón Reemplazar . El controlador del botón crea una matriz que asigna los nombres a los marcadores de posición y, a continuación, reemplaza cada marcador de posición por el nombre asignado.
Puedes usar la herramienta Script Lab para seguir los fragmentos de código que se muestran aquí. En Word, puede cargar el ejemplo "Patrón de objetos correlacionados" o importar este código de ejemplo desde el repositorio de GitHub.
La siguiente instrucción de asignación crea la matriz de asignación entre el marcador de posición y los nombres asignados.
const jobMapping = [
{ job: "{Coordinator}", person: "Sally" },
{ job: "{Deputy}", person: "Bob" },
{ job: "{Manager}", person: "Kim" }
];
El código siguiente muestra cómo podrías reemplazar cada marcador de posición con su nombre asignado si utilizaste context.sync bucles internos. Esto corresponde a la replacePlaceholdersSlow función del ejemplo.
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();
}
}
});
En el código anterior, hay un bucle externo y otro interno. Cada uno de ellos contiene una context.sync llamada. Según el primer fragmento de código de este artículo, probablemente vea que en context.sync el bucle interno simplemente se puede mover después del bucle interno. Pero eso aún dejaría el código con un context.sync (dos de ellos en realidad) en el bucle externo. El código siguiente muestra cómo puedes quitar context.sync de los bucles. Corresponde a la replacePlaceholders función del ejemplo. Discutiremos el código más adelante.
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();
});
Tenga en cuenta que el código usa el patrón de bucle dividido.
- El bucle exterior del ejemplo anterior se ha dividido en dos. (El segundo bucle tiene un bucle interno, que se espera porque el código está iterando sobre un conjunto de trabajos (o marcadores de posición) y dentro de ese conjunto está iterando sobre los rangos coincidentes).
- Hay un
context.syncbucle después de cada principal, pero nocontext.syncdentro de ningún bucle. - El segundo bucle principal itera a través de una matriz que se crea en el primer bucle.
Pero la matriz creada en el primer bucle no contiene solo un objeto de Office como ocurrió en el primer bucle en la sección Lectura de valores del documento con el patrón de bucle dividido. Esto se debe a que parte de la información necesaria para procesar los objetos de rango de Word no está en los objetos de rango en sí, sino que proviene de la jobMapping matriz.
Por lo tanto, los objetos de la matriz creada en el primer bucle son objetos personalizados que tienen dos propiedades. La primera es una matriz de rangos de Word que coinciden con un puesto de trabajo específico (es decir, una cadena de marcador de posición) y la otra es una cadena que proporciona el nombre de la persona asignada al trabajo. Esto hace que el bucle final sea fácil de escribir y de leer, ya que toda la información necesaria para procesar un rango determinado se encuentra en el mismo objeto personalizado que contiene el rango. El nombre que debe reemplazar a correlatedObject.rangesMatchingJob.items[j] es la otra propiedad del mismo objeto: correlatedObject.personAssignedToJob.
Llamamos a esta variación del patrón de bucle dividido el patrón de objetos correlacionados . La idea general es que el primer bucle crea una matriz de objetos personalizados. Cada objeto tiene una propiedad cuyo valor es uno de los elementos de un objeto de colección de Office (o una matriz de dichos elementos). El objeto personalizado tiene otras propiedades, cada una de las cuales proporciona la información necesaria para procesar los objetos de Office en el bucle final. Consulte la sección Otros ejemplos de estos patrones para vincular a un ejemplo en el que el objeto de correlación personalizado tiene más de dos propiedades.
Una advertencia adicional: a veces se necesita más de un bucle solo para crear la matriz de objetos correlacionados personalizados. Esto puede ocurrir si necesita leer una propiedad de cada miembro de un objeto de colección de Office solo para recopilar información que se usará para procesar otro objeto de colección. (Por ejemplo, el código debe leer los títulos de todas las columnas de una tabla de Excel porque el complemento va a aplicar un formato de número a las celdas de algunas columnas en función del título de esa columna). Pero siempre puedes mantener la context.syncs entre los bucles, en lugar de en un bucle. Consulte la sección Otros ejemplos de estos patrones para obtener un ejemplo.
Otros ejemplos de estos patrones
- Para obtener un ejemplo muy simple de Excel que usa
Array.forEachbucles, consulte la respuesta aceptada a esta pregunta de desbordamiento de pila: ¿Es posible poner en cola más de un context.load antes de context.sync? - Para obtener un ejemplo sencillo de Word que utiliza
Array.forEachbucles y no utilizaawaitasync/sintaxis, vea la respuesta aceptada a esta pregunta de desbordamiento de pila: Iterar en todos los párrafos con controles de contenido con la API de JavaScript de Office. - Para obtener un ejemplo de Word avanzado, importe este gist en la herramienta Script Lab. Para obtener contexto en el uso de la esencia, consulte la respuesta aceptada a la pregunta de Stack Overflow El documento no está sincronizado después de reemplazar texto. En este ejemplo se crea un tipo de objeto de correlación personalizado que tiene tres propiedades. Usa un total de tres bucles para construir la matriz de objetos correlacionados y dos bucles más para realizar el procesamiento final. Hay una mezcla de bucles y
forArray.forEach. - Aunque no es estrictamente un ejemplo de los patrones de bucle dividido u objetos correlacionados, hay un ejemplo avanzado de Excel que muestra cómo convertir un conjunto de valores de celda en otras monedas con un solo
context.syncarchivo . Para probarlo, abre la herramienta Script Lab y, a continuación, busca y navega hasta la muestra de convertidor de divisas.
¿Cuándo no debe usar los patrones de este artículo?
Excel en la Web no puede leer más de 5 MB de datos en una llamada context.syncde . Si se excede este límite, se produce un error. (Para obtener más información, vea la sección "Complementos de Excel" de Límites de recursos y optimización del rendimiento de los complementos de Office ). Es muy raro que se acerque a este límite, pero si existe la posibilidad de que esto suceda con el complemento, el código no debe cargar todos los datos en un solo bucle y seguir el bucle con un context.sync. Pero aún debe evitar tener un context.sync en cada iteración de un bucle sobre un objeto de colección. En su lugar, defina subconjuntos de los elementos de la colección y recorra cada subconjunto por turno, con un context.sync entre los bucles. Podría estructurar esto con un bucle externo que itera sobre los subconjuntos y contiene el context.sync en cada una de estas iteraciones externas.