Асинхронное программирование в случае надстроек Office

Важно!

Эта статья относится к общим API, модели API JavaScript для Office, которая была представлена в Office 2013. Эти API-интерфейсы включают такие компоненты, как пользовательский интерфейс, диалоговые окна и параметры клиентов, общие для нескольких типов приложений Office. Надстройки Outlook используют только общие API, в частности подмножество API, предоставленных в объекте Mailbox.

Вы должны использовать общие API только в сценариях, которые не поддерживаются API-интерфейсами для определенных приложений. Сведения о том, как использовать общие API вместо API для определенных приложений, см. в статье Общие сведения об API JavaScript для Office.

Почему в API Надстройки Office используется асинхронное программирование? JavaScript — это язык однопотокового программирования, поэтому если скрипт вызывает продолжительный синхронный процесс, исполнение всех последующих скриптов будет заблокировано до завершения этого процесса. Так как некоторые операции с веб-клиентами Office (но и с настольными клиентами) могут блокировать выполнение, если они выполняются синхронно, большинство API JavaScript для Office предназначены для асинхронного выполнения. Это гарантирует быстрое и быстрое реагирование надстроек Office. При работе с асинхронными методами зачастую требуется создавать функции обратного вызова.

Имена всех асинхронных методов в API заканчиваются на "Async", например Document.getSelectedDataAsyncметоды , Binding.getDataAsyncили Item.loadCustomPropertiesAsync . При вызове асинхронного метода он выполняется немедленно и все дополнительные скрипты могут продолжать работу. Необязательная функция обратного вызова, передаваемая в асинхронный метод, выполняется тогда, когда готовы данные или запрашиваемая операция. Обычно это происходит быстро, но иногда возможен возврат с небольшой задержкой.

На следующей схеме показан поток выполнения для вызова асинхронного метода, который считывает данные, выбранные пользователем в документе, открытом в серверном Word или Excel. На момент выполнения асинхронного вызова поток выполнения JavaScript может выполнять любую дополнительную обработку на стороне клиента (хотя ни один из них не показан на схеме). Когда возвращается метод "Async", обратный вызов возобновляет выполнение в потоке, и надстройка может получить доступ к данным, сделать с ним что-то и отобразить результат. Тот же шаблон асинхронного выполнения сохраняется при работе с клиентскими приложениями Office в Windows или Mac.

Схема, показывающая взаимодействие выполнения команд с течением времени с пользователем, страницей надстройки и сервером веб-приложения, на котором размещена надстройка.

Поддержка этой асинхронной конструкции как в полнофункциональных, так и в веб-клиентах, — это часть стратегии проектирования "однократное написание — запуск на нескольких платформах" модели разработки Надстройки Office. Например, можно создать контентную надстройку или надстройку области задач с одной базой кода, которая будет выполняться как в Excel в Windows, так и в Excel в Интернете.

Запись функции обратного вызова для асинхронного метода

Функция обратного вызова, передаваемая в качестве аргумента обратного вызова методу Async, должна объявить один параметр, который среда выполнения надстройки будет использовать для предоставления доступа к объекту AsyncResult при выполнении функции обратного вызова. Можно записать:

  • Анонимная функция, которая должна быть записана и передана непосредственно в соответствии с вызовом метода Async в качестве параметра обратного вызова метода Async.

  • Именованной функции, передавая имя этой функции в качестве параметра обратного вызова метода Async.

Анонимную функцию удобно использовать, если код такой функции будет использован всего один раз (так как у нее нет имени, вы не сможете сослаться на нее в другой части кода). Именованные функции применяются, если необходимо многократно использовать функцию обратного вызова для нескольких асинхронных методов.

Написание функции анонимного обратного вызова

Следующая анонимная функция обратного вызова объявляет один параметр с именем result , который извлекает данные из свойства AsyncResult.value при возврате обратного вызова.

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

В следующем примере показано, как передать эту анонимную функцию обратного вызова в строке в контексте полного вызова метода Async в Document.getSelectedDataAsync метод .

  • Первый аргумент coercionType , указывает для Office.CoercionType.Textвозврата выбранных данных в виде строки текста.

  • Второй аргумент обратного вызова — это анонимная функция, передаваемая методу в строке. При выполнении функции она использует параметр result для доступа value к свойству AsyncResult объекта для отображения данных, выбранных пользователем в документе.

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

Вы также можете использовать параметр функции обратного вызова для доступа к другим свойствам AsyncResult объекта . Используйте свойство AsyncResult.status, чтобы определить, успешно ли был выполнен вызов. Если не удалось выполнить вызов, можно использовать свойство AsyncResult.error, чтобы получить доступ к объекту Error и получить сведения об ошибке.

Дополнительные сведения об использовании метода см. в getSelectedDataAsync статье Чтение и запись данных в активное выделение в документе или электронной таблице.

Написание именованной функции обратного вызова

Кроме того, можно написать именованную функцию и передать ее имя в параметр обратного вызова метода Async. Например, предыдущий пример можно изменить так, чтобы передавать функцию с именем writeDataCallback в качестве параметра callback.

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

Что возвращается в свойство AsyncResult.value?

Свойства asyncContext, statusи объекта возвращают одни и error те же типы сведений функции обратного вызова, передаваемой во все асинхронные AsyncResult методы. Однако то, что возвращается в AsyncResult.value свойство, зависит от функциональности метода Async.

Например, addHandlerAsync методы (объектов Binding, CustomXmlPart, Document, RoamingSettings и Settings ) используются для добавления функций обработчика событий к элементам, представленным этими объектами. Вы можете получить доступ к свойству AsyncResult.value из функции обратного вызова, передаваемой в любой из addHandlerAsync методов, но так как при добавлении обработчика событий доступ к данным или объекту не осуществляется, value свойство всегда возвращает значение undefined при попытке доступа к нему.

С другой стороны, при вызове Document.getSelectedDataAsync метода он возвращает данные, выбранные пользователем в документе, в AsyncResult.value свойство обратного вызова. Или при вызове метода Bindings.getAllAsync он возвращает массив всех Binding объектов в документе. При вызове метода Bindings.getByIdAsync он возвращает один Binding объект.

Описание возвращаемых свойству AsyncResult.value метода Async см. в разделе "Значение обратного вызова" справочного раздела этого метода. Сводку по всем объектам, которые предоставляют Async методы, см. в таблице в нижней части раздела объекта AsyncResult .

Шаблоны асинхронного программирования

API JavaScript для Office поддерживает два типа шаблонов асинхронного программирования.

  • С использованием вложенных обратных вызовов
  • С использованием шаблона promise

При асинхронном программировании с использованием функций обратного вызова зачастую требуется вкладывать возвращаемый результат одного обратного вызова в один или несколько других обратных вызовов. В этом случае вы можете использовать вложенные обратные вызовы асинхронных методов API.

Использование вложенных обратных вызовов — это шаблон программирования, знакомый большинству разработчиков на языке JavaScript, но код с глубоко вложенными обратными вызовами может быть труден для чтения и понимания. В качестве альтернативы вложенным обратным вызовам API JavaScript для Office также поддерживает реализацию шаблона обещаний.

Примечание.

В текущей версии API JavaScript для Office встроенная поддержка шаблона обещаний работает только с кодом для привязок в электронных таблицах Excel и Word документах. Однако другие функции, имеющие обратные вызовы, можно поместить в собственную пользовательскую функцию promise-returning. Дополнительные сведения см. в статье Перенос общих API в функции promise-returning.

Асинхронное программирование с использованием вложенных функций обратного вызова

Зачастую для какой-либо задачи необходимо выполнять несколько асинхронных операций. Для этого можно вкладывать один асинхронный вызов в другой.

В следующем примере кода показано, как вложить два асинхронных вызова.

  • Сначала вызывается метод Bindings.getByIdAsync для получения доступа к привязке в документе с именем "MyBinding". Объект AsyncResult , возвращаемый параметру result этого обратного вызова, предоставляет доступ к указанному объекту привязки AsyncResult.value из свойства .
  • Затем объект привязки, доступный из первого result параметра, используется для вызова метода Binding.getDataAsync .
  • Наконец, параметр обратного вызова, result2 переданного методу Binding.getDataAsync , используется для отображения данных в привязке.
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; 
}

Этот базовый вложенный шаблон обратного вызова можно использовать для всех асинхронных методов в API JavaScript для Office.

В следующих разделах показано, как использовать анонимные или именованные функции для вложенных обратных вызовов в асинхронных методах.

Использование анонимных функций для вложенных обратных вызовов

В следующем примере две анонимные функции объявляются встроенными и передаются в getByIdAsync методы и getDataAsync как вложенные обратные вызовы. Поскольку это простые и встроенные функции, их назначение сразу же становится понятным.

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

Использование именованных функций для вложенных обратных вызовов

В сложных реализациях может оказаться полезным использовать именованные функции для упрощения чтения, поддержки и повторного использования. В следующем примере две анонимные функции из примера в предыдущем разделе были перезаписаны как функции с именами deleteAllData и showResult. Затем эти именованные функции передаются в getByIdAsync методы и deleteAllDataValuesAsync в качестве обратных вызовов по имени.

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

Асинхронное программирование с применением шаблона, предусматривающего использование обещаний для получения доступа к данным в привязках

Вместо того чтобы передавать функцию обратного вызова и перед продолжением выполнения ждать, пока она будет возвращена, шаблон программирования promise немедленно возвращает объект promise, который представляет ее нужный результат. Однако, в отличие от истинно синхронного программирования, выполнение результата promise на самом деле задерживается до тех пор, пока среда выполнения Надстройки Office не сможет завершить запрос. Если выполнить запрос не удается, используется обработчик onError.

API JavaScript для Office предоставляет функцию Office.select для поддержки шаблона обещаний для работы с существующими объектами привязки. Объект promise, возвращенный в функцию Office.select , поддерживает только четыре метода, к которым можно получить доступ непосредственно из объекта Binding : getDataAsync, setDataAsync, addHandlerAsync и removeHandlerAsync.

Шаблон обещаний для работы с привязками принимает эту форму.

Office.select(selectorExpression, onError).BindingObjectAsyncMethod

Параметр selectorExpression принимает форму "bindings#bindingId", где bindingId — это имя ( id) привязки, созданной ранее в документе или электронной таблице (с помощью одного из методов Bindings addFrom коллекции: addFromNamedItemAsync, addFromPromptAsyncили addFromSelectionAsync). Например, выражение bindings#cities селектора указывает, что вы хотите получить доступ к привязке с идентификатором "cities".

Параметр onError — это функция обработки ошибок, которая принимает один параметр типа AsyncResult , который можно использовать для доступа Error к объекту, если select функции не удается получить доступ к указанной привязке. В следующем примере показана базовая функция обработки ошибки, которую можно передать в параметр 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; 
}

Замените заполнитель BindingObjectAsyncMethod вызовом любого из четырех Binding методов объектов, поддерживаемых объектом promise: getDataAsync, setDataAsync, addHandlerAsyncили removeHandlerAsync. Вызовы этих методов не поддерживают дополнительные шаблоны promise. Их нужно вызывать с помощью шаблона функции вложенного обратного вызова.

Binding После выполнения обещания объекта его можно повторно использовать в вызове метода с цепочкой, как если бы это была привязка (среда выполнения надстройки не будет асинхронно повторять выполнение обещания). Binding Если обещание объекта не может быть выполнено, среда выполнения надстройки попытается снова получить доступ к объекту привязки при следующем вызове одного из его асинхронных методов.

В следующем примере кода функция используется select для получения привязки с id "cities" из Bindings коллекции, а затем вызывается метод addHandlerAsync для добавления обработчика событий для события dataChanged привязки.

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

Важно!

Объектное Binding обещание, возвращаемое функцией Office.select , предоставляет доступ только к четырем Binding методам объекта . Если необходимо получить доступ к любому из других элементов Binding объекта, вместо этого необходимо использовать Document.bindings методы свойства и Bindings.getByIdAsync или Bindings.getAllAsync для получения Binding объекта. Например, если требуется доступ к любому из Binding свойств объекта ( documentсвойства , idили type ) или требуется доступ к свойствам объектов MatrixBinding или TableBinding , необходимо использовать getByIdAsync методы или getAllAsync для получения Binding объекта.

Передача необязательных параметров асинхронным методам

Общий синтаксис для всех асинхронных методов соответствует этому шаблону.

AsyncMethod(RequiredParameters, [OptionalParameters],Обратный вызовFunction);

Все асинхронные методы поддерживают необязательные параметры, которые передаются в виде объекта JavaScript, содержащего один или несколько необязательных параметров. Объект, содержащий необязательные параметры, представляет собой неупорядоченную коллекцию пар "ключ-значение" с символом ":", разделяющим ключ и значение. Каждая пара в объекте разделяется точкой с запятой, а весь набор пар заключен в скобки. Ключом является имя параметра, а значением — значение, которое следует передать этому параметру.

Можно создать объект, содержащий необязательные встроенные параметры, или путем создания options объекта и передачи его в качестве параметра options .

Передача необязательных параметров в строке

Например, синтаксис вызова метода Document.setSelectedDataAsync с необязательными параметрами в качестве встроенных выглядит так:

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

В этой форме синтаксиса вызова два необязательных параметра, coercionType и asyncContext, определяются как анонимный встроенный объект JavaScript, заключенный в фигурные скобки.

В следующем примере показано, как вызвать метод, Document.setSelectedDataAsync указав необязательные встроенные параметры.

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

Примечание.

Необязательные параметры можно указать в объекте параметра в любом порядке, если их имена указаны правильно.

Передача необязательных параметров в объекте options

Кроме того, можно создать объект с именем options , который задает необязательные параметры отдельно от вызова метода, а затем передать объект в options качестве аргумента options .

В следующем примере показан один из способов создания options объекта, где parameter1, value1и т. д. являются заполнителями для фактических имен и значений параметров.

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

Когда указываются параметры ValueFormat и FilterType, код будет таким:

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

Вот еще один способ создания options объекта.

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

Это выглядит как в следующем примере, если используется для указания ValueFormat параметров и FilterType :

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

Примечание.

При использовании любого из методов создания options объекта можно указать необязательные параметры в любом порядке, если их имена указаны правильно.

В следующем примере показано, как вызвать метод путем Document.setSelectedDataAsync указания необязательных параметров в объекте options .

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

В обоих примерах необязательных параметров параметр обратного вызова указывается как последний параметр (после встроенных необязательных параметров или после объекта аргумента options ). Кроме того, можно указать параметр обратного вызова внутри встроенного объекта JavaScript или в объекте options . Однако параметр обратного вызова можно передать только в одном расположении: в options объекте (встроенном или созданном извне) или в качестве последнего параметра, но не в обоих расположениях.

Перенос общих API-интерфейсов в функции promise-returning

Методы Common API (и API Outlook) не возвращают promises. Поэтому вы не можете использовать await для приостановки выполнения до завершения асинхронной операции. Если вам нужно await поведение, можно заключив вызов метода в явно созданное promise.

Базовый шаблон заключается в создании асинхронного метода, который немедленно возвращает объект Promise и разрешает этот объект Promise после завершения внутреннего метода или отклоняет объект в случае сбоя метода. Ниже приведен простой пример.

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

Если эту функцию необходимо ожидать, ее можно вызвать с await помощью ключевое слово или передать в then функцию.

Примечание.

Этот метод особенно полезен, если необходимо вызвать Общий API внутри вызова run функции в объектной модели приложения. Пример функции, используемой getDocumentFilePath таким образом, см. в файлеHome.js в примере Word-Add-in-JavaScript-MDConversion.

Ниже приведен пример использования 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);
                    }
                });
            }
        });
    });
}

См. также