Office 加载项中的异步编程

重要

本文适用于 常见 API,即 Office 2013 中引入的 Office JavaScript API 模型。 这些 API 包括在多种类型的 Office 应用程序中都很常见的 UI、对话框和客户端设置等功能。 Outlook 加载项仅使用通用 API,尤其是通过邮箱对象公开的 API 子集。

对于特定于应用程序的 API 不支持的场景,应仅使用通用 API。 若要了解何时使用通用 API(而非特定于应用程序的 API),请参阅了解 Office JavaScript API

为什么 Office 外接程序 API 使用异步编程? 因为 JavaScript 是单线程语言,如果脚本调用长时间运行的同步进程,则会阻止所有后续脚本执行,直至该进程完成。 由于针对 Office Web 客户端的某些操作 (但桌面客户端以及) 在同步运行时可能会阻止执行,因此大多数 Office JavaScript API 设计为异步执行。 这可确保 Office 加载项响应迅速。 使用这些异步方法时,也通常会要求您编写回调函数。

API 中所有异步方法的名称以“Async”结尾,例如 Document.getSelectedDataAsyncBinding.getDataAsyncItem.loadCustomPropertiesAsync 方法。 调用某个“Async”方法时,该方法会立即执行,并且任何后续脚本执行都可以继续。 传递给“Async”方法的可选回调函数在数据或请求操作准备就绪后便会立即执行。 虽然是立即执行,但在它返回之前可能会略有延迟。

下图显示了对“异步”方法的调用的执行流程,该方法读取用户在基于服务器的Word或 Excel 中打开的文档中选择的数据。 在进行“异步”调用时,JavaScript 执行线程可以自由地执行任何其他客户端处理 (尽管图) 中未显示任何内容。 )当“Async”方法返回时,回调在线程上恢复执行,外接程序可以访问数据、处理数据并显示结果。 在 Windows 或 Mac 上使用 Office 客户端应用程序时,使用相同的异步执行模式。

显示随时间推移与用户、外接程序页和托管外接程序的 Web 应用服务器的命令执行交互的关系图。

在富客户端和 Web 客户端中支持此异步设计是 Office 外接程序开发模型"写入一次,跨平台运行"设计目标的一部分。 例如,可以使用将在 Windows 上的 Excel 和 Excel web 版 中运行的单个代码库创建内容或任务窗格加载项。

为“异步”方法编写回调函数

作为回调参数传递给“Async”方法的 回调 函数必须声明一个参数,外接程序运行时将在回调函数执行时使用该参数来提供对 AsyncResult 对象的访问。 可以编写:

  • 一个匿名函数,该函数必须直接按照对“Async”方法的调用作为“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 属性的内容的差异

对象的 asyncContextstatuserror 属性 AsyncResult 向传递给所有“异步”方法的回调函数返回相同类型的信息。 但是,返回给 AsyncResult.value 属性的内容因“Async”方法的功能而异。

例如,Binding、addHandlerAsyncCustomXmlPartDocumentRoamingSettingsSettings 对象的方法 () 用于向这些对象表示的项添加事件处理程序函数。 可以从传递给任何方法的addHandlerAsync回调函数访问AsyncResult.value属性,但由于添加事件处理程序时不会访问任何数据或对象,value因此如果尝试访问该属性,则始终返回未定义

另一方面,如果调用 Document.getSelectedDataAsync 方法,它会将用户在文档中 AsyncResult.value 选择的数据返回到回调中的 属性。 或者,如果调用 Bindings.getAllAsync 方法,它将返回文档中所有 Binding 对象的数组。 并且,如果调用 Bindings.getByIdAsync 方法,它将返回单个 Binding 对象。

有关返回到 AsyncResult.value 方法属性 Async 的内容的说明,请参阅该方法参考主题的“回调值”部分。 有关提供 Async 方法的所有对象的摘要,请参阅 AsyncResult 对象主题底部的表。

异步编程模式

Office JavaScript API 支持两种类型的异步编程模式。

  • 使用嵌套回调
  • 使用承诺模式

使用回调函数的异步编程通常需要您将回调返回的结果嵌套在两个或更多回调中。 如果您需要这么做,则可以使用来自 API 的所有"Async"方法的嵌套回调。

使用嵌套回调是大多数 JavaScript 开发人员都熟知的编程模式,但使用了深层嵌套回调的代码难以阅读和理解。 作为嵌套回调的替代方法,Office JavaScript API 还支持承诺模式的实现。

注意

在当前版本的 Office JavaScript API 中,对 promises 模式的内置支持仅适用于 Excel 电子表格和Word文档中绑定的代码。 但是,可以在自己的自定义 Promise 返回函数中包装具有回调的其他函数。 有关详细信息,请参阅 在 Promise-returning 函数中包装通用 API

使用嵌套回调函数的异步编程

通常,完成一项任务需要执行两个或更多个异步操作。 为实现此目的,可在一个调用中嵌套另一个"Async"调用。

以下代码示例内嵌两个异步调用。

  • 首先,调用 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; 
}

此基本嵌套回调模式可用于 Office JavaScript API 中的所有异步方法。

以下各节显示如何使用匿名函数或命名函数用于异步方法中的嵌套回调。

对嵌套回调使用匿名函数

在以下示例中,两个匿名函数以内联方式声明,并作为嵌套回调传递到 getByIdAsyncgetDataAsync 方法中。 由于这两个函数简单且为内嵌,因此实现的意图很清晰。

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

对嵌套回调使用命名函数

在复杂实现中,使用命名函数对于提高代码的可读性、可维护性和可重用性可能会有帮助。 在以下示例中,上一部分中示例中的两个匿名函数已重写为名为 和 showResultdeleteAllData函数。 然后,这些命名函数将作为名称回调传递到 getByIdAsyncdeleteAllDataValuesAsync 方法中。

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

使用承诺模式访问绑定中的数据的异步编程

在继续执行之前,承诺编程模式会立即返回表示其预期结果的承诺对象,而不是传递回调函数并等待函数返回。 然而,与真正同步编程不同的是,在 Office 外接程序运行时环境完成请求之前,承诺结果的实现在后台实际上是延迟的。 如果不能达到要求,将提供 onError 处理程序来处理此类情况。

Office JavaScript API 提供 Office.select 函数,以支持使用现有绑定对象的承诺模式。 返回到函数的 Office.select promise 对象仅支持可从 Binding 对象直接访问的四种方法: getDataAsyncsetDataAsyncaddHandlerAsyncremoveHandlerAsync

用于处理绑定的承诺模式采用此形式。

Office.select (selectorExpressiononError) 。BindingObjectAsyncMethod

selectorExpression 参数采用 的形式"bindings#bindingId",其中 bindingId 是之前使用集合的id“addFrom”方法Bindings之一在文档或电子表格中创建的绑定的名称 ( () :addFromNamedItemAsyncaddFromPromptAsyncaddFromSelectionAsync) 。 例如,选择器表达式 bindings#cities 指定你希望访问 ID 为“cities”的绑定。

onError 参数是一个错误处理函数,它采用一个类型的AsyncResult参数,如果函数无法访问指定的绑定,该select参数可用于访问Error对象。 以下示例显示了一个可传递给 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 占位符替换为对 promise 对象支持的四 Binding 个对象方法中的任何一个的调用: getDataAsyncsetDataAsyncaddHandlerAsyncremoveHandlerAsync。 对这些方法的调用不支持其他的承诺。 你必须使用嵌套回调函数模式来调用它们。

Binding实现对象承诺后,可以在链接的方法调用中重复使用它,就像它是绑定一样 (外接程序运行时不会异步重试履行承诺) 。 Binding如果无法实现对象承诺,外接程序运行时将在下次调用其异步方法之一时再次尝试访问绑定对象。

下面的代码示例使用 select 函数从Bindings集合中id检索具有“”cities的绑定,然后调用 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.getByIdAsyncBindings.getAllAsync 方法来检索对象 Binding 。 例如,如果需要访问对象的任何 Binding 属性 (document) 、 idtype 属性,或者需要访问 MatrixBindingTableBinding 对象的属性,则必须使用 getByIdAsyncgetAllAsync 方法来检索 Binding 对象。

将可选参数传递给异步方法

所有“异步”方法的通用语法都遵循此模式。

AsyncMethod(RequiredParameters, [OptionalParameters],CallbackFunction);

所有异步方法都支持可选参数,这些参数作为包含一个或多个可选参数的 JavaScript 对象传入。 包含可选参数的对象是键值对的无序集合,其中“:”字符分隔了键和值。 对象中的每对用逗号分隔,整个对集合括在大括号中。 键是参数名称,值是要为该参数传递的值。

可以以内联方式创建包含可选参数的对象,也可以创建对象 options 并将其作为 options 参数传入。

内联传递可选参数

例如,用可选参数内嵌调用 Document.setSelectedDataAsync 方法的语法类似如下:

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

在此形式的调用语法中,两个可选参数 coercionTypeasyncContext 定义为括在大括号中的匿名 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 对象的一种方法,其中 parameter1value1等是实际参数名称和值的占位符。

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

用于指定 ValueFormatFilterType 参数时与以下示例类似。

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

下面是创建 options 对象的另一种方法。

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

当用于指定 ValueFormatFilterType 参数时,如以下示例所示:

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

注意

使用创建对象的任一 options 方法时,只要正确指定了可选参数的名称,就可以按任意顺序指定可选参数。

以下示例演示如何通过在 对象中options指定可选参数来Document.setSelectedDataAsync调用 方法。

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 对象中 (内联或) 外部创建,或作为最后一个参数传递,但不能同时传递这两个位置。

在 Promise-returning 函数中包装通用 API

通用 API (和 Outlook API) 方法不返回 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));
        }
    })
}

当需要等待此函数时,可以使用 关键字 (keyword) 调用await它,也可以将其传递给then函数。

注意

当需要在应用程序特定对象模型中的函数调用内部调用通用 API 时, run 此方法特别有用。 有关以这种方式使用的函数的示例getDocumentFilePath,请参阅示例 Word-Add-in-JavaScript-MDConversion 中的文件Home.js

下面是使用 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);
                    }
                });
            }
        });
    });
}

另请参阅