Compartir a través de


Llamadas de función personalizada de Batch para un servicio remoto

Si las funciones personalizadas llaman a un servicio remoto puede usar un modelo de procesamiento por lotes para reducir el número de llamadas de red al servicio remoto. Para reducir los recorridos de ida y vuelta de la red, deberá procesar por lotes todas las llamadas en una sola llamada al servicio web. Esto es ideal cuando se vuelve a calcular la hoja de cálculo.

Por ejemplo, si alguien usa la función personalizada en 100 celdas en una hoja de cálculo y después vuelve a calcular la hoja de cálculo, la función personalizada se ejecutaría 100 veces y realizaría 100 llamadas de red. Al usar un patrón de procesamiento por lotes, las llamadas pueden combinarse para realizar los 100 cálculos en una sola llamada de red.

Importante

Tenga en cuenta que las funciones personalizadas están disponibles en Excel en las siguientes plataformas.

  • Office en la web
  • Office en Windows
    • Suscripción a Microsoft 365
    • Retail perpetual Office 2016 y versiones posteriores
    • Office 2021 perpetua con licencia por volumen y versiones posteriores
  • Office en Mac

Las funciones personalizadas de Excel no se admiten actualmente en lo siguiente:

  • Office en iPad
  • versiones perpetuas con licencia por volumen de Office 2019 o versiones anteriores en Windows

Vea el ejemplo completo

Para ver el ejemplo completado, siga este artículo y pegue los ejemplos de código en su propio proyecto. Por ejemplo, para crear un nuevo proyecto de función personalizado para TypeScript, use el generador de Yeoman para complementos de Office y, a continuación, agregue todo el código de este artículo al proyecto. Ejecute el código y pruébelo.

Como alternativa, descargue o vea el proyecto de ejemplo completo en Patrón de procesamiento por lotes de funciones personalizadas. Si desea ver el código en su totalidad antes de seguir leyendo, eche un vistazo al archivo de script.

Creación del modelo de procesamiento por lotes en este artículo

Para configurar el procesamiento por lotes de funciones personalizadas debe escribir tres secciones principales de código.

  1. Una operación de inserción para agregar una nueva operación al lote de llamadas cada vez que Excel llama a la función personalizada.
  2. Función para realizar la solicitud remota cuando el lote está listo.
  3. Código de servidor para responder a la solicitud por lotes, calcular todos los resultados de la operación y devolver los valores.

En las secciones siguientes, aprenderá a construir el código de un ejemplo a la vez. Se recomienda crear un proyecto de funciones personalizadas con el generador de Yeoman para complementos de Office . Para crear un nuevo proyecto, consulte Introducción al desarrollo de funciones personalizadas de Excel. Puede usar TypeScript o JavaScript.

Procesamiento por lotes de cada llamada a la función personalizada

Las funciones personalizadas funcionan mediante una llamada a un servicio remoto para realizar la operación y calcular el resultado que necesitan. Esto les proporciona una forma para almacenar cada operación en un lote. Más adelante verá cómo crear una función _pushOperation para procesar por lotes las operaciones. En primer lugar, veamos el siguiente ejemplo de código para ver cómo llamar a _pushOperation desde la función personalizada.

En el siguiente código, la función personalizada realiza la división, pero se basa en un servicio remoto para realizar el cálculo real. Llama a _pushOperation para procesar por lotes la operación junto con otras operaciones en el servicio remoto. Asigna a la operación el nombre div2. Puede usar cualquier esquema de nombre que desee para operaciones siempre que el servicio remoto use también el mismo esquema (podrá obtener más información sobre el servicio remoto más adelante). Además, se pasan los argumentos que necesitará el servicio remoto para ejecutar la operación.

Adición de la función personalizada div2

Agregue el código siguiente al archivo defunctions.js o functions.ts (en función de si usó JavaScript o 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]);
}

Adición de variables globales para realizar el seguimiento de solicitudes por lotes

A continuación, agregue dos variables globales al archivo functions.js o functions.ts . _isBatchedRequestScheduled es importante más adelante para el control de tiempo de las llamadas por lotes al servicio remoto.

let _batch = [];
let _isBatchedRequestScheduled = false;

Agregar la _pushOperation función

Cuando Excel llama a la función personalizada, debe insertar la operación en la matriz por lotes. El siguiente código de función _pushOperation muestra cómo agregar una nueva operación desde una función personalizada. Crea una nueva entrada de lote, crea una nueva promesa resolver o rechazar la operación, e inserta la entrada en la matriz de lote.

Este código también comprueba si se ha programado un lote. En este ejemplo, cada lote está programado para ejecutarse cada 100 ms. Puede ajustar este valor según sea necesario. Los valores más altos tienen como resultado que se envíen lotes más grandes al servicio remoto y un mayor tiempo de espera para que el usuario pueda ver los resultados. Los valores más bajos suelen enviar más lotes al servicio remoto, pero con un tiempo de respuesta rápido para los usuarios.

La función crea un objeto invocationEntry que contiene el nombre de cadena de la operación que se va a ejecutar. Por ejemplo, si tiene dos funciones personalizadas denominadas multiply y divide, puede reutilizarlas como los nombres de operación en las entradas del lote. args contiene los argumentos que se pasaron a la función personalizada desde Excel. Por último, resolve o reject los métodos almacenan una promesa que contiene la información que devuelve el servicio remoto.

Agregue el código siguiente al archivo functions.js o 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;
}

Realizar la solicitud remota

La finalidad de la función _makeRemoteRequest es pasar el lote de operaciones al servicio remoto y devolver los resultados a cada función personalizada. Primero crea una copia de la matriz de lote. Esto permite que las llamadas de función personalizada simultáneas de Excel empiecen inmediatamente a procesarse por lotes en una nueva matriz. Después la copia se transforma en una matriz más sencilla que no contiene la información de promesa. No tendría sentido pasar las promesas a un servicio remoto, ya que no funcionarían. La _makeRemoteRequest rechazará o resolverá cada promesa en función de lo que devuelva el servicio remoto.

Agregue el código siguiente al archivo functions.js o 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);
  }
}

Modifique _makeRemoteRequest para su solución

La función _makeRemoteRequest llama a _fetchFromRemoteService que, como verá más adelante, es solo un boceto que representa el servicio remoto. Esto facilita estudiar y ejecutar el código en este artículo. Pero si desea usar este código para un servicio remoto real, debe realizar los siguientes cambios.

  • Decidir cómo serializar las operaciones de lote a través de la red. Por ejemplo, es posible que desee colocar la matriz en un cuerpo JSON.
  • En lugar de llamar a _fetchFromRemoteService, debe realizar la llamada de red real al servicio remoto pasando el lote de operaciones.

Procesamiento de la llamada por lotes en el servicio remoto

El último paso es controlar la llamada por lotes en el servicio remoto. El siguiente código de ejemplo muestra la función _fetchFromRemoteService. Esta función descomprime cada operación, realiza la operación especificada y devuelve los resultados. Para descubrir el propósito de este artículo, la función _fetchFromRemoteService está diseñada para ejecutarse en el complemento web y simular un servicio remoto. Puede agregar este código al archivofunctions.js o functions.ts para que pueda estudiar y ejecutar todo el código de este artículo sin tener que configurar un servicio remoto real.

Agregue el código siguiente al archivo functions.js o 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));
}

Modificar _fetchFromRemoteService para que el servicio remoto dinámico

Para modificar la _fetchFromRemoteService función para que se ejecute en el servicio remoto en directo, realice los siguientes cambios.

  • Dependiendo de la plataforma de servidor (Node.js u otras), asigne la llamada de red del cliente a esta función.
  • Quite la función pause que simula la latencia de red como parte del simulacro.
  • Modifique la declaración de función para trabajar con el parámetro pasado si se cambia el parámetro para fines de red. Por ejemplo, en lugar de una matriz, sería un cuerpo JSON de operaciones por lotes para procesar.
  • Modifique la función para realizar las operaciones (o llamar a las funciones que realicen las operaciones).
  • Aplique un mecanismo de autenticación adecuado. Asegúrese de que solo los autores de llamadas correctos pueden obtener acceso a la función.
  • Coloque el código en el servicio remoto.

Siguientes pasos

Obtenga información sobre los diversos parámetros que puede usar en las funciones personalizadas. O revise los conceptos básicos tras realizar una llamada de web a través de una función personalizada.

Vea también