Programação assíncrona em Suplementos do Office

Importante

Este artigo se aplica às APIs Comuns, o modelo de API JavaScript do Office que foi introduzido com o Office 2013. Essas APIs incluem recursos como interface de usuário, caixas de diálogo e configurações de cliente, que são comuns entre vários tipos de aplicativos do Office. Os suplementos do Outlook usam exclusivamente APIs comuns, especialmente o subconjunto de APIs expostos por meio do objetoCaixa de Correio.

Você só deve usar APIs comuns para cenários que não têm suporte por APIs específicas do aplicativo. Para saber quando usar APIs comuns em vez de APIs específicas do aplicativo, confira Entendendo a API de JavaScript do Office.

Por que a API de Suplementos do Office usa a programação assíncrona? Como o JavaScript é uma linguagem de thread único, se o script invocar um processo síncrono demorado, todas as execuções subsequentes do script serão bloqueadas até que o processo seja concluído. Como determinadas operações contra clientes Web do Office (mas também clientes de área de trabalho) podem bloquear a execução se forem executadas de forma síncrona, a maioria das APIs JavaScript do Office são projetadas para serem executadas de forma assíncrona. Isso garante que os suplementos do Office sejam responsivos e rápidos. Em geral, isso também requer que você escreva funções de retorno de chamada ao trabalhar com esses métodos assíncronos.

Os nomes de todos os métodos assíncronos na API terminam com "Async", como os Document.getSelectedDataAsyncmétodos , Binding.getDataAsyncou Item.loadCustomPropertiesAsync . Quando um método "Async" é chamado, ele é executado imediatamente e qualquer execução subsequente do script poderá continuar. A função de retorno de chamada opcional que você passar para um método de "Async" é executada assim que os dados ou a operação solicitada está pronta. Isso geralmente ocorre imediatamente, mas pode haver um pequeno atraso antes de retornar.

O diagrama a seguir mostra o fluxo de execução de uma chamada para um método "Assíncrono" que lê os dados que o usuário selecionou em um documento aberto no Word ou Excel baseado no servidor. No ponto em que a chamada "Async" é feita, o thread de execução javaScript é gratuito para executar qualquer processamento adicional do lado do cliente (embora nenhum seja mostrado no diagrama). Quando o método "Async" retorna, o retorno de chamada retoma a execução no thread e o suplemento pode acessar dados, fazer algo com ele e exibir o resultado. O mesmo padrão de execução assíncrono contém ao trabalhar com aplicativos cliente do Office no Windows ou no Mac.

Diagrama mostrando a interação de execução de comando ao longo do tempo com o usuário, a página de suplemento e o servidor de aplicativo Web que hospeda o suplemento.

O suporte a esse design assíncrono em clientes Web e avançados faz parte das metas de design "gravar plataforma cruzada já executada" do modelo de desenvolvimento de Suplementos do Office. Por exemplo, você pode criar um suplemento de conteúdo ou painel de tarefas com uma única base de código que será executada no Excel no Windows e Excel na Web.

Gravar a função de retorno de chamada para um método "Assíncrono"

A função de retorno de chamada que você passa como o argumento de retorno de chamada para um método "Async" deve declarar um único parâmetro que o runtime de suplemento usará para fornecer acesso a um objeto AsyncResult quando a função de retorno de chamada for executada. Você pode gravar:

  • Uma função anônima que deve ser escrita e passada diretamente de acordo com a chamada para o método "Async" como o parâmetro de retorno de chamada do método "Async".

  • Uma função nomeada, passando o nome dessa função como o parâmetro de retorno de chamada de um método "Async".

Uma função anônima é útil se você só for usar seu código uma vez - porque ele não possui um nome, você não pode referenciá-la em outra parte do seu código. Uma função nomeada é útil se você quiser reutilizar a função retorno de chamada para mais de um método "Async".

Gravar uma função de retorno de chamada anônima

A função de retorno de chamada anônima a seguir declara um único parâmetro chamado result que recupera dados da propriedade AsyncResult.value quando o retorno de chamada retorna.

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

O exemplo a seguir mostra como passar essa função de retorno de chamada anônima na linha no contexto de uma chamada completa do método "Async" para o Document.getSelectedDataAsync método.

  • O primeiro argumento coercionType , Office.CoercionType.Text, especifica para retornar os dados selecionados como uma cadeia de texto.

  • O segundo argumento de retorno de chamada é a função anônima passada na linha para o método. Quando a função é executada, ela usa o parâmetro de resultado para acessar a value propriedade do AsyncResult objeto para exibir os dados selecionados pelo usuário no 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; 
}

Você também pode usar o parâmetro da função de retorno de chamada para acessar outras propriedades do AsyncResult objeto. Use a propriedade AsyncResult.status para determinar se a chamada teve êxito ou falhou. Se sua chamada falhar, você pode usar a propriedade AsyncResult.error para acessar um objeto Error para informações sobre o erro.

Para obter mais informações sobre como usar o getSelectedDataAsync método, consulte Ler e gravar dados na seleção ativa em um documento ou planilha.

Gravar uma função de retorno de chamada nomeada

Como alternativa, você pode escrever uma função nomeada e passar seu nome para o parâmetro de retorno de chamada de um método "Async". Por exemplo, o exemplo anterior pode ser reescrito para transmitir uma função chamada writeDataCallback como o parâmetro callback assim.

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

Diferenças entre o que é retornado para a propriedade AsyncResult.value

As asyncContextpropriedades , statuse error do AsyncResult objeto retornam os mesmos tipos de informações para a função de retorno de chamada passada para todos os métodos "Async". No entanto, o que é retornado à AsyncResult.value propriedade varia dependendo da funcionalidade do método "Assíncrono".

Por exemplo, os addHandlerAsync métodos (dos objetos Binding, CustomXmlPart, Document, RoamingSettings e Settings) são usados para adicionar funções de manipulador de eventos aos itens representados por esses objetos. Você pode acessar a AsyncResult.value propriedade a partir da função de retorno de chamada que você passa para qualquer um dos addHandlerAsync métodos, mas como nenhum dado ou objeto está sendo acessado quando você adiciona um manipulador de eventos, a value propriedade sempre retorna indefinida se você tentar acessá-la.

Por outro lado, se você chamar o Document.getSelectedDataAsync método, ele retornará os dados que o usuário selecionou no documento para a AsyncResult.value propriedade no retorno de chamada. Ou, se você chamar o método Bindings.getAllAsync , ele retornará uma matriz de todos os Binding objetos no documento. E, se você chamar o método Bindings.getByIdAsync , ele retornará um único Binding objeto.

Para obter uma descrição do que é retornado à AsyncResult.value propriedade para um Async método, consulte a seção "Valor de retorno de chamada" do tópico de referência desse método. Para obter um resumo de todos os objetos que fornecem Async métodos, consulte a tabela na parte inferior do tópico de objeto AsyncResult .

Padrões de programação assíncrona

A API JavaScript do Office dá suporte a dois tipos de padrões de programação assíncronos.

  • Usando retornos de chamada aninhados
  • Usando o padrão de promessas

A programação assíncrona com funções de retorno de chamada frequentemente exigem que você aninhe o resultado retornado de um retorno de chamada dentro de dois ou mais retornos de chamada. Se você precisar fazer isso, é possível usar retornos de chamada aninhados de todos os métodos "Async" da API.

Usar retornos de chamada aninhados é um padrão de programação familiar para a maioria dos desenvolvedores de JavaScript, mas códigos com retornos de chamada profundamente aninhados podem ser difíceis de ler e entender. Como alternativa aos retornos de chamada aninhados, a API JavaScript do Office também dá suporte a uma implementação do padrão de promessas.

Observação

Na versão atual da API JavaScript do Office, o suporte interno para o padrão de promessas funciona apenas com código para associações em planilhas do Excel e documentos Word. No entanto, você pode envolver outras funções que têm retornos de chamada dentro de sua própria função de retorno de promessa personalizada. Para obter mais informações, consulte Encapsular APIs comuns em funções de retorno de promessa.

Programação assíncrona usando funções aninhadas de retorno de chamada

Frequentemente, você precisa executar duas ou mais operações assíncronas para concluir uma tarefa. Para fazer isso, você pode aninhar uma chamada "Async" dentro de outra.

O exemplo de código a seguir aninha duas ou mais chamadas assíncronas.

  • Primeiro, o método Bindings.getByIdAsync é chamado para acessar uma associação no documento chamado "MyBinding". O AsyncResult objeto retornado ao result parâmetro desse retorno de chamada fornece acesso ao objeto de associação especificado da AsyncResult.value propriedade.
  • Em seguida, o objeto de associação acessado do primeiro result parâmetro é usado para chamar o método Binding.getDataAsync .
  • Por fim, o result2 parâmetro do retorno de chamada passado para o Binding.getDataAsync método é usado para exibir os dados na associação.
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; 
}

Esse padrão de retorno de chamada aninhado básico pode ser usado para todos os métodos assíncronos na API JavaScript do Office.

As seções a seguir mostram como usar funções anônimas ou nomeadas para retornos de chamada aninhados em métodos assíncronos.

Usar funções anônimas para retornos de chamada aninhados

No exemplo a seguir, duas funções anônimas são declaradas embutidas e passadas para os getByIdAsync métodos e getDataAsync como retornos de chamada aninhados. Como as funções são simples e embutidas, a intenção da implementação fica imediatamente clara.

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

Usar funções nomeadas para retornos de chamada aninhados

Em implementações complexas, pode ser útil usar funções nomeadas para facilitar a leitura, manutenção e reutilização do seu código. No exemplo a seguir, as duas funções anônimas do exemplo na seção anterior foram reescritas como funções nomeadas deleteAllData e showResult. Essas funções nomeadas são então passadas para os getByIdAsync métodos e deleteAllDataValuesAsync como retornos de chamada pelo nome.

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

Programação assíncrona usando o padrão de promessas para acessar dados em associações

Em vez de transmitir a função de retorno de chamada e aguardar até que a função retorne antes da continuação da execução, o padrão de programação de promessas retorna imediatamente retorna um objeto de promessa que representa o resultado desejado. No entanto, ao contrário da verdadeira programação síncrona, nos bastidores o cumprimento do resultado prometido é, na verdade, adiado até que o ambiente de tempo de execução dos Suplementos do Office possa concluir a solicitação. Um manipulador onError é fornecido para atender a situações em que a solicitação não pode ser cumprida.

A API JavaScript do Office fornece a função Office.select para dar suporte ao padrão de promessas para trabalhar com objetos de associação existentes. O objeto promise retornado à Office.select função dá suporte apenas aos quatro métodos que você pode acessar diretamente do objeto Binding : getDataAsync, setDataAsync, addHandlerAsync e removeHandlerAsync.

O padrão de promessas para trabalhar com associações toma esse formulário.

Office.select(selectorExpression, onError).BindingObjectAsyncMethod

O parâmetro selectorExpression usa o formulário "bindings#bindingId", em que bindingId é o nome ( id) de uma associação que você criou anteriormente no documento ou planilha (usando um dos métodos "addFrom" da Bindings coleção: addFromNamedItemAsync, addFromPromptAsyncou addFromSelectionAsync). Por exemplo, a expressão bindings#cities seletor especifica que você deseja acessar a associação com uma id de "cidades".

O parâmetro onError é uma função de tratamento de erro que usa um único parâmetro de tipo AsyncResult que pode ser usado para acessar um Error objeto, se a select função não acessar a associação especificada. O exemplo a seguir mostra uma função de manipulador de erro básica que pode ser transmitida para o 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; 
}

Substitua o espaço reservado BindingObjectAsyncMethod por uma chamada para qualquer um dos quatro Binding métodos de objeto compatíveis com o objeto promise: getDataAsync, setDataAsync, addHandlerAsyncou removeHandlerAsync. As chamadas para esses métodos não oferecem suporte a promessas adicionais. Você deve chamá-los usando o padrão de função de retorno de chamada aninhado.

Depois que uma Binding promessa de objeto for cumprida, ela pode ser reutilizado na chamada de método encadeado como se fosse uma associação (o runtime de suplemento não tentará assíncronamente cumprir a promessa). Se a Binding promessa do objeto não puder ser cumprida, o runtime de suplemento tentará novamente acessar o objeto de associação na próxima vez que um de seus métodos assíncronos for invocado.

O exemplo de código a seguir usa a select função para recuperar uma associação com o id "cities" da Bindings coleção e, em seguida, chama o método addHandlerAsync para adicionar um manipulador de eventos para o evento dataChanged da associação.

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

Importante

A Binding promessa de objeto retornada pela Office.select função fornece acesso apenas aos quatro métodos do Binding objeto. Se você precisar acessar qualquer um dos outros membros do Binding objeto, em vez disso, você deve usar a Document.bindings propriedade e Bindings.getByIdAsync ou Bindings.getAllAsync os métodos para recuperar o Binding objeto. Por exemplo, se você precisar acessar qualquer uma das Binding propriedades do objeto (as documentid, ou type propriedades) ou precisar acessar as propriedades dos objetos MatrixBinding ou TableBinding, você deve usar os getByIdAsync métodos ou getAllAsync para recuperar um Binding objeto.

Passar parâmetros opcionais para métodos assíncronos

A sintaxe comum para todos os métodos "Assíncronos" segue esse padrão.

AsyncMethod(Parameters obrigatórios, [OpcionalParameters],CallbackFunction);

Todos os métodos assíncronos dão suporte a parâmetros opcionais, que são passados como um objeto JavaScript que contém um ou mais parâmetros opcionais. O objeto que contém os parâmetros opcionais é uma coleção não ordenada de pares chave-valor com o caractere ":" separando a chave e o valor. Cada par do objeto é separado por vírgula e o conjunto completo de pares é incluído entre chaves. A chave é o nome do parâmetro e o valor é o valor a ser transmitido para esse parâmetro.

Você pode criar o objeto que contém parâmetros opcionais embutidos ou criando um options objeto e passando-o como o parâmetro de opções .

Passar parâmetros opcionais embutidos

Por exemplo, a sintaxe para chamar o método Document.setSelectedDataAsync com parâmetros opcionais embutidos tem esta aparência:

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

Nesta forma da sintaxe de chamada, os dois parâmetros opcionais, coercionType e asyncContext, são definidos como um objeto JavaScript anônimo embutido em chaves.

O exemplo a seguir mostra como chamar o Document.setSelectedDataAsync método especificando parâmetros opcionais embutidos.

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

Observação

Você pode especificar parâmetros opcionais em qualquer ordem no objeto de parâmetro, desde que seus nomes sejam especificados corretamente.

Passar parâmetros opcionais em um objeto de opções

Como alternativa, você pode criar um objeto chamado options que especifica os parâmetros opcionais separadamente da chamada do método e, em seguida, passar o options objeto como o argumento de opções .

O exemplo a seguir mostra uma maneira de criar o options objeto, em parameter1que , value1e assim por diante, são espaços reservados para os nomes e valores de parâmetro reais.

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

Que é semelhante ao exemplo a seguir quando usado para especificar os parâmetros ValueFormat e FilterType.

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

Aqui está outra maneira de criar o options objeto.

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

Que se parece com o exemplo a seguir quando usado para especificar os ValueFormat parâmetros e FilterType :

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

Observação

Ao usar qualquer método de criação do options objeto, você pode especificar parâmetros opcionais em qualquer ordem, desde que seus nomes sejam especificados corretamente.

O exemplo a seguir mostra como chamar o Document.setSelectedDataAsync método especificando parâmetros opcionais em um 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; 
}

Em ambos os exemplos de parâmetro opcional, o parâmetro de retorno de chamada é especificado como o último parâmetro (seguindo os parâmetros opcionais embutidos ou seguindo o objeto de argumento de opções ). Como alternativa, você pode especificar o parâmetro de retorno de chamada dentro do objeto JavaScript embutido ou no options objeto. No entanto, você pode passar o parâmetro de retorno de chamada em apenas um local: no options objeto (embutido ou criado externamente) ou como o último parâmetro, mas não em ambos.

Envolver APIs comuns em funções de retorno de promessa

Os métodos de API Comum (e API do Outlook) não retornam Promessas. Portanto, você não pode usar o await para pausar a execução até que a operação assíncrona seja concluída. Se você precisar de await comportamento, poderá encerrar a chamada de método em uma Promessa criada explicitamente.

O padrão básico é criar um método assíncrono que retorna um objeto Promise imediatamente e resolve o objeto Promise quando o método interno é concluído ou rejeita o objeto se o método falhar. Apresentamos um exemplo simples a seguir.

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

Quando essa função precisa ser aguardada, ela pode ser chamada com o await palavra-chave ou passada para uma then função.

Observação

Essa técnica é especialmente útil quando você precisa chamar uma API Comum dentro de uma chamada da run função em um modelo de objeto específico do aplicativo. Para obter um exemplo da getDocumentFilePath função que está sendo usada dessa forma, consulte o arquivoHome.js no exemplo Word-Add-in-JavaScript-MDConversion.

A seguir está um exemplo usando 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);
                    }
                });
            }
        });
    });
}

Confira também