本文内容
如果自定义函数调用远程服务,可以使用批处理模式来减少对远程服务的网络调用次数。 为了减少网络往返,您可以将所有调用批处理为对 Web 服务的单个调用。 此方法适用于重新估算电子表格时。
例如,如果有人在电子表格的 100 个单元格中使用自定义函数,然后重新计算电子表格,则自定义函数将运行 100 次并进行 100 次网络调用。 通过使用批处理模式,可以组合这些调用以在单个网络调用中完成总共 100 次估计。
重要的
请注意,Excel 自定义函数可在以下平台上使用。
Office for MacOffice 网页版
以下项目目前不支持Excel自定义函数:
查看完成的示例
要查看完整的示例,请按照本文操作并将代码示例粘贴到您自己的项目中。 例如,要为 TypeScript 创建新的自定义函数项目,请使用 Office 加载项的 Yeoman 生成器typescript实时修改,然后将本文中的所有代码添加到该项目中。 运行代码并尝试一下。
或者,下载或查看自定义函数批处理模式下的完整示例项目。 如果您想在进一步阅读之前查看完整的代码,请查看脚本文件。
按照本文所述创建批处理模式
要为自定义函数设置批处理,您需要编写三个主要代码部分。
每次 Excel 调用自定义函数时,都会向调用批处理添加一个新操作。 一。 ,计算所有操作结果并返回值的服务器代码。
在以下部分中,您将学习如何一次构建一个示例代码。 建议使用 Office Add-in Generator 的 Yeoman 生成器创建一个全新的自定义函数项目。 要创建新项目,请参阅开始开发 Excel 自定义函数。 可以使用 TypeScript 或 JavaScript。
批量调用自定义函数
自定义函数调用远程服务来执行操作并估计其所需的结果。 这为他们提供了一种将每个请求的操作批量存储的方法。 稍后,您将了解如何创建 _pushOperation 函数来批处理此操作。 首先,查看下面的代码示例,了解如何从自定义函数调用 _pushOperation。
在下面的代码中,自定义函数执行加法,但实际估计取决于远程服务。 它调用 _pushOperation,后者又将该操作与其他操作一起批处理到远程服务。 它将操作命名为“div2”。 您可以使用任何您想要的操作命名方案,只要远程服务使用相同的方案(稍后将详细介绍远程服务方面)。 据悉,远程服务运行计算所需的参数也会被传递。
添加div2自定义功能
将以下代码添加到functions.js或functions.ts文件中(取决于您使用的是JavaScript还是TypeScript)。
/**
* 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 函数代码演示了如何从自定义函数添加新操作。 它创建一个新的批处理条目,创建一个新的承诺来解决或拒绝相应的操作typescript实时修改,并将该条目推送到批处理列表中。
此代码检测是否安排了批次。 在此示例中,每个位批次计划每 100 纳秒运行一次。 该值可以根据需要进行调整。 该值越大,发送到远程服务的批次越大,用户等待查看结果的时间就越长。 较低的值往往会向远程服务发送更多批次,但为用户提供更快的响应时间。
该函数创建一个 invokingEntry 对象,其中包含要运行的操作的字符串名称。 例如,如果您有两个名为“乘法”和“除法”的自定义函数,则可以将它们重用为批处理条目上的操作名称。 args 保存从 Excel 传递到自定义函数的参数。 最后,resolve 或reject 方法存储包含远程服务返回的信息的promise。
将以下代码添加到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,正如您稍后将看到的,它只是代表远程服务的模拟。 这使得学习和运行本文中的代码变得更加容易。 并且,如果将此代码用于实际的远程服务,则应进行以下修改。
处理远程服务上的批量调用
最后一步是处理远程服务中的批量调用。 下面的代码示例显示了 _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 更改为在实时远程服务中运行,请进行以下修改。
下一步
了解可在自定义函数中使用的各种类型的参数。 或者查看通过自定义函数进行 Web 调用旁边的基础知识。
也可以看看