远程服务的 Batch 自定义函数调用
如果自定义函数调用远程服务,可以使用批处理模式来减少对远程服务的网络调用次数。 为了减少网络往返,你可以将所有调用批处理为对 Web 服务的单个调用。 当重新计算电子表格时,此方法非常合适。
例如,如果有人在电子表格的 100 个单元格中使用了自定义函数,然后重新计算电子表格,则自定义函数将运行 100 次并进行 100 次网络调用。 通过使用批处理模式,可以将这些调用组合起来,在单次网络调用中完成总共 100 次计算。
重要
请注意,以下平台上可以使用 Excel 自定义函数。
- Office 网页版
- Windows 版 Office
- Microsoft 365 订阅
- 零售永久 Office 2016 及更高版本
- 批量许可永久Office 2021及更高版本
- Mac 版 Office
以下各项当前不支持 Excel 自定义函数:
- iPad 版 Office
- Windows 上 Office 2019 或更早版本的批量许可永久版本
查看已完成的示例
若要查看已完成的示例,请遵循本文并将代码示例粘贴到自己的项目中。 例如,若要为 TypeScript 创建新的自定义函数项目,请使用 Office 外接程序的 Yeoman 生成器,然后将本文中的所有代码添加到项目中。 运行代码并试用。
或者,在 自定义函数批处理模式中下载或查看完整的示例项目。 如果要在进一步阅读之前查看完整代码,请查看脚本文件。
创建本文中所述的批处理模式
若要为自定义函数设置批处理,需要编写三个主要的代码部分。
- 一个 推送操作 ,用于在 Excel 每次调用自定义函数时向调用批添加新操作。
- 一个 函数,用于在批处理准备就绪时发出远程请求 。
- 用于响应批处理请求、计算所有操作结果并返回值的服务器代码。
在以下部分中,你将了解如何一次构造一个示例代码。 建议使用 Office 加载项生成器的 Yeoman 生成器 创建一个全新的自定义函数项目。 若要创建新项目,请参阅 开始开发 Excel 自定义函数。 可以使用 TypeScript 或 JavaScript。
批处理对自定义函数的每次调用
自定义函数通过调用远程服务来执行运算并计算其所需的结果。 这为它们提供了一种将每个请求的运算存储到批处理中的方法。 稍后,你将看到如何创建 _pushOperation
函数来批处理这些运算。 首先,看看下面的代码示例,以了解如何从自定义函数调用 _pushOperation
。
在下面的代码中,自定义函数执行除法,但实际计算依赖于远程服务。 它调用 _pushOperation
,从而将该运算与其他运算一起批处理到远程服务。 它将该运算命名为“div2”。 你可以为运算使用任何所需的命名方案,只要远程服务也使用相同的方案即可(稍后将对远程服务方面进行详细介绍)。 此外,还将传递远程服务运行该运算所需的参数。
添加 div2 自定义函数
根据是否使用 JavaScript 或 TypeScript) ,将以下代码添加到functions.js或 functions.ts 文件 (。
/**
* Divides two numbers using batching
* @CustomFunction
* @param dividend The number being divided
* @param divisor The number the dividend is divided by
* @returns The result of dividing the two numbers
*/
function div2(dividend, divisor) {
return _pushOperation("div2", [dividend, divisor]);
}
添加用于跟踪批处理请求的全局变量
接下来,将两个全局变量添加到 functions.js 或 functions.ts 文件。
_isBatchedRequestScheduled
对于对远程服务的批处理调用进行计时,这一点非常重要。
let _batch = [];
let _isBatchedRequestScheduled = false;
_pushOperation
添加 函数
当 Excel 调用自定义函数时,需要将操作推送到批处理数组中。 以下 _pushOperation 函数代码演示如何从自定义函数添加新操作。 它会创建一个新的批处理条目,创建一个新的承诺来解决或拒绝相应运算,并将该条目推送到批处理数组中。
此段代码还会检查是否对批处理进行了安排。 在本例中,将每个批处理安排为每 100 毫秒运行一次。 可以根据需要调整此值。 值越大,发送到远程服务的批处理越大,用户查看结果的等待时间越长。 较低的值倾向于向远程服务发送更多的批处理,但可为用户提供较快的响应时间。
函数创建一个 invocationEntry 对象,该对象包含要运行的操作的字符串名称。 例如,如果有两个分别名为 multiply
和 divide
的自定义函数,则可以在批处理条目中将它们作为运算名称重复使用。
args
保存从 Excel 传递到自定义函数的参数。 最后, resolve
或 reject
方法存储包含远程服务返回的信息的承诺。
将以下代码添加到 functions.js 或 functions.ts 文件。
// This function encloses your custom functions as individual entries,
// which have some additional properties so you can keep track of whether or not
// a request has been resolved or rejected.
function _pushOperation(op, args) {
// Create an entry for your custom function.
console.log("pushOperation");
const invocationEntry = {
operation: op, // e.g., sum
args: args,
resolve: undefined,
reject: undefined,
};
// Create a unique promise for this invocation,
// and save its resolve and reject functions into the invocation entry.
const promise = new Promise((resolve, reject) => {
invocationEntry.resolve = resolve;
invocationEntry.reject = reject;
});
// Push the invocation entry into the next batch.
_batch.push(invocationEntry);
// If a remote request hasn't been scheduled yet,
// schedule it after a certain timeout, e.g., 100 ms.
if (!_isBatchedRequestScheduled) {
console.log("schedule remote request");
_isBatchedRequestScheduled = true;
setTimeout(_makeRemoteRequest, 100);
}
// Return the promise for this invocation.
return promise;
}
发出远程请求
_makeRemoteRequest
函数的目的是将一批运算传递给远程服务,然后将结果返回给每个自定义函数。 它首先创建批处理数组的副本。 这样,来自 Excel 的并发自定义函数调用便可以立即在新数组中开始批处理。 然后将副本转换为不包含承诺信息的较简单的数组。 将这些承诺传递给远程服务是没有意义的,因为它们不会发生作用。
_makeRemoteRequest
将根据远程服务返回的内容拒绝或解决每个承诺。
将以下代码添加到 functions.js 或 functions.ts 文件。
// This is a private helper function, used only within your custom function add-in.
// You wouldn't call _makeRemoteRequest in Excel, for example.
// This function makes a request for remote processing of the whole batch,
// and matches the response batch to the request batch.
function _makeRemoteRequest() {
// Copy the shared batch and allow the building of a new batch while you are waiting for a response.
// Note the use of "splice" rather than "slice", which will modify the original _batch array
// to empty it out.
try{
console.log("makeRemoteRequest");
const batchCopy = _batch.splice(0, _batch.length);
_isBatchedRequestScheduled = false;
// Build a simpler request batch that only contains the arguments for each invocation.
const requestBatch = batchCopy.map((item) => {
return { operation: item.operation, args: item.args };
});
console.log("makeRemoteRequest2");
// Make the remote request.
_fetchFromRemoteService(requestBatch)
.then((responseBatch) => {
console.log("responseBatch in fetchFromRemoteService");
// Match each value from the response batch to its corresponding invocation entry from the request batch,
// and resolve the invocation promise with its corresponding response value.
responseBatch.forEach((response, index) => {
if (response.error) {
batchCopy[index].reject(new Error(response.error));
console.log("rejecting promise");
} else {
console.log("fulfilling promise");
console.log(response);
batchCopy[index].resolve(response.result);
}
});
});
console.log("makeRemoteRequest3");
} catch (error) {
console.log("error name:" + error.name);
console.log("error message:" + error.message);
console.log(error);
}
}
根据自己的解决方案修改 _makeRemoteRequest
_makeRemoteRequest
函数调用 _fetchFromRemoteService
,正如稍后将会看到的,后者只是一个表示远程服务的模拟。 这使得研究和运行本文中的代码更加容易。 但是,如果要将此代码用于实际的远程服务,则应进行以下更改。
- 决定如何通过网络将批处理运算序列化。 例如,你可能希望将数组放入 JSON 主体中。
- 你不需要调用
_fetchFromRemoteService
,而是需要对传递批量运算的远程服务进行实际的网络调用。
处理远程服务上的批处理调用
最后一步是处理远程服务中的批处理调用。 下面的代码示例展示了 _fetchFromRemoteService
函数。 此函数会解包每个运算,执行指定的运算,并返回结果。 出于学习目的,在本文中,_fetchFromRemoteService
函数适用于在 Web 加载项中运行并模拟远程服务。 可以将此代码添加到 functions.js 或 functions.ts 文件,以便可以研究并运行本文中的所有代码,而无需设置实际的远程服务。
将以下代码添加到 functions.js 或 functions.ts 文件。
// This function simulates the work of a remote service. Because each service
// differs, you will need to modify this function appropriately to work with the service you are using.
// This function takes a batch of argument sets and returns a promise that may contain a batch of values.
// NOTE: When implementing this function on a server, also apply an appropriate authentication mechanism
// to ensure only the correct callers can access it.
async function _fetchFromRemoteService(requestBatch) {
// Simulate a slow network request to the server.
console.log("_fetchFromRemoteService");
await pause(1000);
console.log("postpause");
return requestBatch.map((request) => {
console.log("requestBatch server side");
const { operation, args } = request;
try {
if (operation === "div2") {
// Divide the first argument by the second argument.
return {
result: args[0] / args[1]
};
} else if (operation === "mul2") {
// Multiply the arguments for the given entry.
const myResult = args[0] * args[1];
console.log(myResult);
return {
result: myResult
};
} else {
return {
error: `Operation not supported: ${operation}`
};
}
} catch (error) {
return {
error: `Operation failed: ${operation}`
};
}
});
}
function pause(ms) {
console.log("pause");
return new Promise((resolve) => setTimeout(resolve, ms));
}
根据自己的实时远程服务修改 _fetchFromRemoteService
若要修改函数 _fetchFromRemoteService
以在实时远程服务中运行,请进行以下更改。
- 根据服务器平台(Node.js 或其他平台),将客户端网络调用映射到此函数。
- 删除作为模拟的一部分来模拟网络延迟的
pause
函数。 - 修改函数声明,以便在出于网络目的更改传递的参数时使用该参数。 例如,它可能是要处理的批量运算的 JSON 主体,而不是数组。
- 修改函数以执行运算(或调用执行运算的函数)。
- 应用相应的身份验证机制。 确保只有正确的调用者才可以访问该函数。
- 将代码放入远程服务。
后续步骤
了解可以在自定义函数中使用的各种参数。 或者查阅通过自定义函数进行 Web 调用后面的基础知识。