Поделиться через


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

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

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

Ключевые моменты

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

  1. Операция отправки для добавления новой операции в пакет вызовов каждый раз, когда Excel вызывает пользовательскую функцию.
  2. Функция для выполнения удаленного запроса, когда пакет готов.
  3. Код сервера для ответа на пакетный запрос, вычисления всех результатов операции и возврата значений.

Важно!

Обратите внимание, что настраиваемые функции доступны в Excel на следующих платформах.

  • Office в Интернете
  • Office для Windows
    • Подписка на Microsoft 365
    • Розничный бессрочный Office 2016 и более поздних версий
    • корпоративный бессрочный/LTSC Office 2021 и более поздних версий
  • Office для Mac

Пользовательские функции Excel в настоящее время не поддерживаются в следующих приложениях:

  • Office для iPad
  • корпоративные бессрочные версии Office 2021 или более ранних версий в Windows

Примечание.

В настоящее время унифицированный манифест для Microsoft 365 не поддерживает проекты пользовательских функций. Для проектов пользовательских функций необходимо использовать только манифест надстройки. Дополнительные сведения см. в разделе Манифест надстроек Office.

Создание шаблона пакетирования в этой статье

В следующих разделах вы узнаете, как создать код по одному примеру за раз. Рекомендуется создать новый проект пользовательских функций с помощью генератора Yeoman для надстроек Office . Сведения о создании проекта см. в статье Начало разработки пользовательских функций Excel. Вы можете использовать TypeScript или JavaScript.

Совет

Чтобы просмотреть готовый пример, создайте новый проект пользовательских функций с генератором Yeoman для надстроек Office. Скопируйте и вставьте примеры кода в проект, затем запустите код и опробуйте его.

Кроме того, можно скачать или просмотреть полный пример проекта в разделе Шаблон пакетной обработки пользовательских функций. Если вы хотите просмотреть весь код перед продолжением, просмотрите файл скрипта.

Включение в пакет каждого вызова пользовательской функции

Пользовательские функции работают путем вызова удаленной службы для выполнения операций и возврата результатов. Это дает возможность сохранения каждой запрашиваемой операции в пакете. Далее вы узнаете, как создать функцию _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 коде функции показано, как добавить новую операцию из пользовательской функции. Здесь создается новый элемент пакета, новое обещание для выполнения или отклонения операции, и элемент вставляется в пакетный массив.

В данном коде также проверяется, является ли пакет плановым. В этом примере выполнение пакете планируется каждые 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, которая запускается в вашей веб-надстройке и имитирует удаленную службу. Этот код можно добавить в файлfunctions.js или functions.ts , чтобы изучить и выполнить весь код, приведенный в этой статье, без необходимости настраивать реальную удаленную службу.

Добавьте следующий код в файлfunctions.js или functions.ts .

// This function simulates the work of a remote service. Because each service
// differs, modify this function as needed 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, содержащий требуемые пакетные операции.
  • Переделайте функцию для выполнения операций (или вызова функций, которые выполняют операции).
  • Примените подходящий механизм проверки подлинности. Убедитесь, что доступ к функции есть только у предусмотренных вами вызывающих пользователей.
  • Поместите код в удаленную службу.

Когда следует избежать пакетной обработки

Пакетная обработка добавляет небольшую задержку и дополнительный код. Избегайте пакетной обработки в следующих сценариях.

Сценарий Негативное влияние пакетной обработки Рекомендация
Один или очень мало вызовов Дополнительное ожидание таймера Вызовите службу напрямую, если список по-прежнему пуст
Очень большие входные данные на вызов Запрос может стать слишком большим Ограничение размера или отправка этих вызовов в одиночку
Некоторые вызовы выполняются гораздо медленнее, чем другие Один медленный вызов задерживает более быстрые Группирование медленных типов по отдельности
Требуется почти мгновенный результат (менее 50 мс) Таймер добавляет задержку Использование более короткого таймера или пропуск пакетной обработки
Сервер уже объединяет работу Нет преимущества Пропуск пакетной обработки на клиенте

Дальнейшие действия

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

Дополнительные ресурсы