Compartir a través de


Programación asíncrona en los complementos de Office

Importante

Este artículo se aplica a las API comunes, el modelo de API de JavaScript de Office que se introdujo con Office 2013. Estas API incluyen características como la interfaz de usuario, los cuadros de diálogo y la configuración del cliente, que son comunes a varios tipos de aplicaciones de Office. Los complementos de Outlook usan exclusivamente API comunes, especialmente los subconjuntos de API que se exponen a través del objeto Buzón.

Solo debe usar las API comunes para escenarios que no son compatibles con las API específicas de aplicaciones. Para saber cuándo usar las API comunes en lugar de las API específicas de la aplicación, consulte Comprender la API de JavaScript de Office.

¿Por qué la API de Complementos de Office usa programación asincrónica? Debido a que JavaScript es un lenguaje de procesamiento único, si un script invoca un proceso sincrónico de larga duración, toda la ejecución posterior de dicho script se bloqueará hasta que el proceso haya finalizado. Dado que ciertas operaciones en clientes web de Office (pero también en clientes de escritorio) podrían bloquear la ejecución si se ejecutan de forma sincrónica, la mayoría de las API de JavaScript de Office están diseñadas para ejecutarse de forma asincrónica. Esto garantiza que los complementos de Office respondan y sean rápidos. Asimismo, al trabajar con estos métodos asincrónicos es frecuente que sea necesario escribir funciones de devolución de llamada.

Los nombres de todos los métodos asincrónicos de la API terminan con "Async", como los Document.getSelectedDataAsyncmétodos , Binding.getDataAsynco Item.loadCustomPropertiesAsync . Cuando un método se llama "Async", se ejecuta inmediatamente y cualquier ejecución posterior del script puede continuar. La función de devolución de llamada opcional que se pasa a un método "Async" se ejecuta en cuanto están listos los datos o la operación solicitada. Esto suele producirse de inmediato, pero puede haber un ligero retraso antes de que se devuelvan datos.

En el diagrama siguiente se muestra el flujo de ejecución de una llamada a un método "Async" que lee los datos seleccionados por el usuario en un documento abierto en Word o Excel basado en servidor. En el momento en que se realiza la llamada a "Async", el subproceso de ejecución de JavaScript es libre de realizar cualquier procesamiento adicional del lado cliente (aunque no se muestra ninguno en el diagrama). Cuando se devuelve el método "Async", la devolución de llamada reanuda la ejecución en el subproceso y el complemento puede acceder a los datos, hacer algo con él y mostrar el resultado. El mismo patrón de ejecución asincrónica se mantiene al trabajar con aplicaciones cliente de Office en Windows o en Mac.

Diagrama que muestra la interacción de ejecución de comandos a lo largo del tiempo con el usuario, la página del complemento y el servidor de aplicaciones web que hospeda el complemento.

La compatibilidad con este diseño asincrónico, tanto en clientes enriquecidos como web, es parte del objetivo de diseño de "escribe una vez, ejecuta en cualquier plataforma" del modelo de desarrollo de los Complementos de Office. Por ejemplo, puede crear un complemento de contenido o panel de tareas con una base de código única que se ejecutará en Excel en Windows y Excel en la web.

Escritura de la función de devolución de llamada para un método "asincrónico"

La función de devolución de llamada que se pasa como argumento de devolución de llamada a un método "Async" debe declarar un único parámetro que el tiempo de ejecución del complemento usará para proporcionar acceso a un objeto AsyncResult cuando se ejecute la función de devolución de llamada. Puede escribir:

  • Función anónima que debe escribirse y pasarse directamente en línea con la llamada al método "Async" como parámetro de devolución de llamada del método "Async".

  • Una función con nombre, pasando el nombre de esa función como parámetro de devolución de llamada de un método "Async".

Una función anónima es útil si solo va a usar su código una vez: puesto que no tiene nombre, no se puede hacer referencia a ella en otra parte del código. Una función con nombre es útil si desea volver a usar la función de devolución de llamada para más de un método "Async".

Escritura de una función de devolución de llamada anónima

La siguiente función de devolución de llamada anónima declara un único parámetro denominado result que recupera datos de la propiedad AsyncResult.value cuando se devuelve la devolución de llamada.

function (result) {
    write('Selected data: ' + result.value);
}

En el ejemplo siguiente se muestra cómo pasar esta función de devolución de llamada anónima en línea en el contexto de una llamada completa al Document.getSelectedDataAsync método "Async".

  • El primer argumento coercionType , Office.CoercionType.Text, especifica que se devuelvan los datos seleccionados como una cadena de texto.

  • El segundo argumento de devolución de llamada es la función anónima que se pasa en línea al método . Cuando se ejecuta la función, usa el parámetro result para acceder a la value propiedad del AsyncResult objeto para mostrar los datos seleccionados por el usuario en el documento.

Office.context.document.getSelectedDataAsync(Office.CoercionType.Text, 
    function (result) {
        write('Selected data: ' + result.value);
    }
});

// Function that writes to a div with id='message' on the page.
function write(message){
    document.getElementById('message').innerText += message; 
}

También puede usar el parámetro de la función de devolución de llamada para acceder a otras propiedades del AsyncResult objeto. Use la propiedad AsyncResult.status para determinar si la llamada se realizó correctamente o produjo errores. Si la llamada produce errores, puede usar la propiedad AsyncResult.error para tener acceso a un objeto Error y obtener información sobre el error.

Para obtener más información sobre el uso del getSelectedDataAsync método , consulte Lectura y escritura de datos en la selección activa en un documento o hoja de cálculo.

Escritura de una función de devolución de llamada con nombre

Como alternativa, puede escribir una función con nombre y pasar su nombre al parámetro de devolución de llamada de un método "Async". Por ejemplo, el ejemplo anterior se puede reescribir para transferir una función denominada writeDataCallback como el parámetro callback, tal y como se indica a continuación.

Office.context.document.getSelectedDataAsync(Office.CoercionType.Text, 
    writeDataCallback);

// Callback to write the selected data to the add-in UI.
function writeDataCallback(result) {
    write('Selected data: ' + result.value);
}

// Function that writes to a div with id='message' on the page.
function write(message){
    document.getElementById('message').innerText += message;
}

Diferencias en lo que se devuelve a la propiedad AsyncResult.value

Las asyncContextpropiedades , statusy error del AsyncResult objeto devuelven los mismos tipos de información a la función de devolución de llamada que se pasa a todos los métodos "asincrónicos". Sin embargo, lo que se devuelve a la AsyncResult.value propiedad varía en función de la funcionalidad del método "Async".

Por ejemplo, los addHandlerAsync métodos (de los objetos Binding, CustomXmlPart, Document, RoamingSettings y Settings ) se usan para agregar funciones de controlador de eventos a los elementos representados por estos objetos. Puede tener acceso a la AsyncResult.value propiedad desde la función de devolución de llamada que pase a cualquiera de los addHandlerAsync métodos, pero dado que no se tiene acceso a ningún objeto ni a datos al agregar un controlador de eventos, la value propiedad siempre devuelve undefined si intenta acceder a ella.

Por otro lado, si llama al Document.getSelectedDataAsync método , devuelve los datos que el usuario seleccionó en el documento a la AsyncResult.value propiedad de la devolución de llamada. O bien, si llama al método Bindings.getAllAsync , devuelve una matriz de todos los Binding objetos del documento. Y, si llama al método Bindings.getByIdAsync , devuelve un solo Binding objeto.

Para obtener una descripción de lo que se devuelve a la AsyncResult.value propiedad de un Async método, vea la sección "Valor de devolución de llamada" del tema de referencia de ese método. Para obtener un resumen de todos los objetos que proporcionan Async métodos, vea la tabla de la parte inferior del tema del objeto AsyncResult .

Patrones de programación asincrónica

La API de JavaScript de Office admite dos tipos de patrones de programación asincrónica.

  • Uso de funciones anidadas de devolución de llamada
  • Uso del patrón de promesas

La programación asincrónica con funciones de devolución de llamada requiere con frecuencia que se anide el resultado devuelto de una devolución de llamada en dos o más devoluciones de llamada. Si debe hacerlo, puede usar las funciones anidadas de devolución de llamada de todos los métodos "Async" de la API.

El uso de devoluciones de llamada anidadas es un patrón de programación conocido por la mayoría de los desarrolladores de JavaScript, pero el código con devoluciones de llamada excesivamente anidadas puede resultar difícil de leer y comprender. Como alternativa a las devoluciones de llamada anidadas, la API de JavaScript de Office también admite una implementación del patrón de promesas.

Nota:

En la versión actual de la API de JavaScript de Office, la compatibilidad integrada con el patrón de promesas solo funciona con código para enlaces en hojas de cálculo de Excel y documentos de Word. Sin embargo, puede encapsular otras funciones que tienen devoluciones de llamada dentro de su propia función personalizada de devolución de promesa. Para obtener más información, vea Encapsular las API comunes en las funciones que devuelven promesas.

Programación asincrónica con funciones anidadas de devolución de llamada

A menudo, hay que llevar a cabo dos operaciones asincrónicas o más para finalizar una tarea. Para hacerlo, puede anidar una llamada "Async" dentro de otra.

En el siguiente código de ejemplo se han anidado dos llamadas asincrónicas.

  • Primero se llama al método Bindings.getByIdAsync para tener acceso a un enlace en el documento denominado "MyBinding". El AsyncResult objeto devuelto al result parámetro de esa devolución de llamada proporciona acceso al objeto de enlace especificado desde la AsyncResult.value propiedad .
  • A continuación, se usa el objeto de enlace al que se accede desde el primer result parámetro para llamar al método Binding.getDataAsync .
  • Por último, el result2 parámetro de la devolución de llamada que se pasa al Binding.getDataAsync método se usa para mostrar los datos en el enlace.
function readData() {
    Office.context.document.bindings.getByIdAsync("MyBinding", function (result) {
        result.value.getDataAsync({ coercionType: 'text' }, function (result2) {
            write(result2.value);
        });
    });
}

// Function that writes to a div with id='message' on the page.
function write(message){
    document.getElementById('message').innerText += message; 
}

Este patrón de devolución de llamada anidado básico se puede usar para todos los métodos asincrónicos en la API de JavaScript de Office.

Las secciones siguientes muestran cómo usar las funciones (anónimas o con nombre) para realizar devoluciones de llamadas anidadas en métodos asincrónicos.

Uso de funciones anónimas para devoluciones de llamada anidadas

En el ejemplo siguiente, dos funciones anónimas se declaran insertadas y se pasan a los getByIdAsync métodos y getDataAsync como devoluciones de llamada anidadas. Como las funciones son sencillas y están alineadas, el propósito de la implementación es claro inmediatamente.

Office.context.document.bindings.getByIdAsync('myBinding', function (bindingResult) {
    bindingResult.value.getDataAsync(function (getResult) {
        if (getResult.status == Office.AsyncResultStatus.Failed) {
            write('Action failed. Error: ' + asyncResult.error.message);
        } else {
            write('Data has been read successfully.');
        }
    });
});

// Function that writes to a div with id='message' on the page.
function write(message){
    document.getElementById('message').innerText += message;
}

Uso de funciones con nombre para devoluciones de llamada anidadas

En implementaciones complejas, puede resultarle útil usar funciones con nombre para facilitar la lectura, el mantenimiento y la reutilización del código. En el ejemplo siguiente, las dos funciones anónimas del ejemplo de la sección anterior se han reescrito como funciones denominadas deleteAllData y showResult. A continuación, estas funciones con nombre se pasan a los getByIdAsync métodos y deleteAllDataValuesAsync como devoluciones de llamada por nombre.

Office.context.document.bindings.getByIdAsync('myBinding', deleteAllData);

function deleteAllData(asyncResult) {
    asyncResult.value.deleteAllDataValuesAsync(showResult);
}

function showResult(asyncResult) {
    if (asyncResult.status == Office.AsyncResultStatus.Failed) {
        write('Action failed. Error: ' + asyncResult.error.message);
    } else {
        write('Data has been deleted successfully.');
    }
}

// Function that writes to a div with id='message' on the page.
function write(message){
    document.getElementById('message').innerText += message;
}

Programación asincrónica con el patrón de promesas para obtener acceso a los datos de los enlaces

En lugar de pasar una función de devolución de la llamada y esperar a que la función obtenga la devolución antes de que continúe la ejecución, el patrón de programación de promesas devuelve inmediatamente un objeto de promesa que representa el resultado esperado. Sin embargo, al contrario de lo que sucede con la programación sincrónica verdadera, en secreto, el resultado prometido está aplazado hasta que un entorno en tiempo de ejecución de complemento de Office pueda completar la solicitud. Se proporciona un controlador onError para cubrir situaciones en las que no se puede cumplir con la solicitud.

La API de JavaScript de Office proporciona la función Office.select para admitir el patrón de promesas para trabajar con objetos de enlace existentes. El objeto promise devuelto a la Office.select función solo admite los cuatro métodos a los que puedes acceder directamente desde el objeto Binding : getDataAsync, setDataAsync, addHandlerAsync y removeHandlerAsync.

El patrón de promesas para trabajar con enlaces adopta este formato.

Office.select(selectorExpression, onError).BindingObjectAsyncMethod

El parámetro selectorExpression toma el formato "bindings#bindingId", donde bindingId es el nombre ( id) de un enlace que creó anteriormente en el documento o hoja de cálculo (mediante uno de los métodos "addFrom" de la Bindings colección: addFromNamedItemAsync, addFromPromptAsynco addFromSelectionAsync). Por ejemplo, la expresión bindings#cities de selector especifica que desea tener acceso al enlace con un identificador de "cities".

El parámetro onError es una función de control de errores que toma un único parámetro de tipo AsyncResult que se puede usar para tener acceso a un Error objeto, si la select función no puede acceder al enlace especificado. En el siguiente ejemplo, se muestra una función de tratamiento de errores básica que se puede transferir al parámetro onError.

function onError(result){
    const err = result.error;
    write(err.name + ": " + err.message);
}

// Function that writes to a div with id='message' on the page.
function write(message){
    document.getElementById('message').innerText += message; 
}

Reemplace el marcador de posición BindingObjectAsyncMethod por una llamada a cualquiera de los cuatro Binding métodos de objeto admitidos por el objeto de promesa: getDataAsync, setDataAsync, addHandlerAsynco removeHandlerAsync. Las llamadas a estos métodos no son compatibles con promesas adicionales. Es necesario llamarlos con el patrón de función de devolución de llamada anidada.

Una vez que se cumple una Binding promesa de objeto, se puede reutilizar en la llamada al método encadenado como si fuera un enlace (el tiempo de ejecución del complemento no volverá a intentar de forma asincrónica cumplir la promesa). Si no se puede cumplir la Binding promesa de objeto, el tiempo de ejecución del complemento intentará de nuevo acceder al objeto de enlace la próxima vez que se invoque uno de sus métodos asincrónicos.

En el siguiente ejemplo de código se usa la select función para recuperar un enlace con "idcities" de la Bindings colección y, a continuación, se llama al método addHandlerAsync para agregar un controlador de eventos para el evento dataChanged del enlace.

function addBindingDataChangedEventHandler() {
    Office.select("bindings#cities", function onError(){/* error handling code */}).addHandlerAsync(Office.EventType.BindingDataChanged,
    function (eventArgs) {
        doSomethingWithBinding(eventArgs.binding);
    });
}

Importante

La Binding promesa de objeto devuelta por la Office.select función proporciona acceso solo a los cuatro métodos del Binding objeto. Si necesita tener acceso a cualquiera de los demás miembros del Binding objeto, en su lugar debe usar la Document.bindings propiedad o Bindings.getByIdAsyncBindings.getAllAsync los métodos para recuperar el Binding objeto. Por ejemplo, si necesita tener acceso a cualquiera de las Binding propiedades del objeto (las documentpropiedades , ido type ), o necesita tener acceso a las propiedades de los objetos MatrixBinding o TableBinding , debe usar los getByIdAsync métodos o getAllAsync para recuperar un Binding objeto.

Pasar parámetros opcionales a métodos asincrónicos

La sintaxis común para todos los métodos "Async" sigue este patrón.

AsyncMethod(RequiredParameters, [OptionalParameters],CallbackFunction);

Todos los métodos asincrónicos admiten parámetros opcionales, que se pasan como un objeto JavaScript que contiene uno o varios parámetros opcionales. El objeto que contiene los parámetros opcionales es una colección desordenada de pares clave-valor con el carácter ":" que separa la clave y el valor. Cada par del objeto está separado por una coma y el conjunto entero de pares está delimitado por dos corchetes. La clave es el nombre del parámetro y el valor es el valor a pasar para ese parámetro.

Puede crear el objeto que contiene parámetros opcionales insertados, o bien crear un options objeto y pasarlo como parámetro options .

Pasar parámetros opcionales insertados

Por ejemplo, la sintaxis para llamar al método Document.setSelectedDataAsync con parámetros opcionales de forma alineada tiene el aspecto siguiente:

 Office.context.document.setSelectedDataAsync(data, {coercionType: 'coercionType', asyncContext: 'asyncContext'},callback);

En esta forma de sintaxis de llamada, los dos parámetros opcionales, coercionType y asyncContext, se definen como un objeto de JavaScript anónimo insertado entre llaves.

En el Document.setSelectedDataAsync ejemplo siguiente se muestra cómo llamar al método especificando parámetros opcionales insertados.

Office.context.document.setSelectedDataAsync(
    "<html><body>hello world</body></html>",
    {coercionType: "html", asyncContext: 42},
    function(asyncResult) {
        write(asyncResult.status + " " + asyncResult.asyncContext);
    }
)

// Function that writes to a div with id='message' on the page.
function write(message){
    document.getElementById('message').innerText += message; 
}

Nota:

Puede especificar parámetros opcionales en cualquier orden en el objeto parameter siempre y cuando sus nombres se especifiquen correctamente.

Pasar parámetros opcionales en un objeto options

Como alternativa, puede crear un objeto denominado options que especifique los parámetros opcionales por separado de la llamada al método y, a continuación, pasar el options objeto como argumento options .

En el ejemplo siguiente se muestra una forma de crear el options objeto, donde parameter1, value1, etc., son marcadores de posición para los nombres y valores de parámetros reales.

const options = {
    parameter1: value1,
    parameter2: value2,
    ...
    parameterN: valueN
};

Es similar al ejemplo siguiente cuando se usa para especificar los parámetros ValueFormat y FilterType.

const options = {
    valueFormat: "unformatted",
    filterType: "all"
};

Esta es otra forma de crear el options objeto.

const options = {};
options[parameter1] = value1;
options[parameter2] = value2;
...
options[parameterN] = valueN;

Que es similar al ejemplo siguiente cuando se usa para especificar los ValueFormat parámetros y FilterType :

const options = {};
options["ValueFormat"] = "unformatted";
options["FilterType"] = "all";

Nota:

Al usar cualquiera de los métodos para crear el options objeto, puede especificar parámetros opcionales en cualquier orden siempre y cuando sus nombres se especifiquen correctamente.

En el Document.setSelectedDataAsync ejemplo siguiente se muestra cómo llamar al método especificando parámetros opcionales en un options objeto .

const options = {
   coercionType: "html",
   asyncContext: 42
};

document.setSelectedDataAsync(
    "<html><body>hello world</body></html>",
    options,
    function(asyncResult) {
        write(asyncResult.status + " " + asyncResult.asyncContext);
    }
)

// Function that writes to a div with id='message' on the page.
function write(message){
    document.getElementById('message').innerText += message; 
}

En ambos ejemplos de parámetros opcionales, el parámetro de devolución de llamada se especifica como el último parámetro (siguiendo los parámetros opcionales insertados o siguiendo el objeto de argumento options ). Como alternativa, puede especificar el parámetro de devolución de llamada dentro del objeto JavaScript insertado o en el options objeto . Sin embargo, puede pasar el parámetro de devolución de llamada en una sola ubicación: en el options objeto (insertado o creado externamente) o como último parámetro, pero no en ambos.

Encapsular las API comunes en funciones que devuelven promesas

Los métodos De API común (y API de Outlook) no devuelven Promesas. Por lo tanto, no puede usar await para pausar la ejecución hasta que se complete la operación asincrónica. Si necesita await comportamiento, puede encapsular la llamada al método en una promesa creada explícitamente.

El patrón básico consiste en crear un método asincrónico que devuelve un objeto Promise inmediatamente y resuelve ese objeto Promise cuando se completa el método interno o rechaza el objeto si se produce un error en el método. A continuación puede ver un ejemplo simple.

function getDocumentFilePath() {
    return new OfficeExtension.Promise(function (resolve, reject) {
        try {
            Office.context.document.getFilePropertiesAsync(function (asyncResult) {
                resolve(asyncResult.value.url);
            });
        }
        catch (error) {
            reject(WordMarkdownConversion.errorHandler(error));
        }
    })
}

Cuando es necesario esperar a esta función, se puede llamar a ella con la await palabra clave o pasarla a una then función.

Nota:

Esta técnica es especialmente útil cuando se necesita llamar a una API común dentro de una llamada de la función en un modelo de objetos específico de la run aplicación. Para obtener un ejemplo de la getDocumentFilePath función que se usa de esta manera, vea el archivo Home.js en el ejemplo Word-Add-in-JavaScript-MDConversion.

A continuación se muestra un ejemplo con TypeScript.

readDocumentFileAsync(): Promise<any> {
    return new Promise((resolve, reject) => {
        const chunkSize = 65536;
        const self = this;

        Office.context.document.getFileAsync(Office.FileType.Compressed, { sliceSize: chunkSize }, (asyncResult) => {
            if (asyncResult.status === Office.AsyncResultStatus.Failed) {
                reject(asyncResult.error);
            } else {
                // `getAllSlices` is a Promise-wrapped implementation of File.getSliceAsync.
                self.getAllSlices(asyncResult.value).then(result => {
                    if (result.IsSuccess) {
                        resolve(result.Data);
                    } else {
                        reject(asyncResult.error);
                    }
                });
            }
        });
    });
}

Vea también