Существует ли способ получать данные из любых генераторов?

Вопрос или проблема

У меня есть функция

async function* values(input) {
    ...
}

которая будет генерировать значения в зависимости от входных данных.

Теперь у меня есть набор входных данных, давайте скажем [1,2,3,4,5], и я хочу создать другую генераторную функцию, которая будет использовать эти входные данные и вызывать values для каждого из них.
Я хочу возвращать значения из оборачивающей генераторной функции всякий раз, когда какой-либо из вызовов values генерирует значение.
Функция генерирует значения последовательно:

function* generateValues() {
     const inputs = [1,2,3,4,5];

     const generators = inputs.map(input => values(input));

     for (const generator of generators) {
          yield await generator.next();
     }
}

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

Я хотел бы заменить цикл for выше на что-то вроде:

for await (const value of Generator.any(generators)) {
    yield value;
}

т.е. я ищу нечто похожее на Generator.any, которое было бы похоже на Promise.any, но без отбрасывания оставшихся генераторов.

Программа, представленная ниже, поддерживает последовательность nexts обещаний, так что nexts[i] разрешается в значение, возвращаемое generators[i] и индекс i. Цикл использует Promise.any многократно для получения самого раннего такого значения (из любого генератора) и обновляет nexts[i] обещанием следующего значения из того же генератора. Но если генератор завершен, обещание фактически удаляется из nexts. Цикл завершается, когда все обещания были удалены.

В этом примере генератор values генерирует два значения: первое приходит тем позже, чем меньше его input, второе приходит тем раньше, чем меньше его input.

async function* values(input) {
  await new Promise(function (resolve, reject) {
    setTimeout(resolve, 1000 - input * 100);
  });
  yield input;
  await new Promise(function (resolve, reject) {
    setTimeout(resolve, input * 200);
  });
  yield input + 10;
}

async function awaitNext(generator, index) {
  const next = await generator.next();
  return { index, next };
}

async function* Generator_any(generators) {
  let running = generators.length;
  const nexts = generators.map(awaitNext);
  while (true) {
    const { index, next } = await Promise.any(nexts);
    if (next.done) {
      // Удалить это обещание
      nexts[index] = new Promise(() => {});
      if (--running === 0) return; // Все обещания удалены
    } else {
      nexts[index] = awaitNext(generators[index], index); // Обновить это обещание для следующего значения
      yield next.value;
    }
  }
}

(async function () {
  const inputs = [1, 2, 3, 4, 5];
  const generators = inputs.map((input) => values(input));
  for await (const i of Generator_any(generators)) console.log(i);
})();

Ответ или решение

Вопрос, который вы задали, касается создания функции-генератора, которая будет поочередно извлекать значения из нескольких асинхронных генераторов, возвращая их по мере их готовности. Это можно реализовать, используя механизм промисов для отслеживания возвращаемых значений от генераторов. Давайте разберем данный процесс.

Асинхронные Генераторы

Предположим, что у нас уже есть функция-генератор values, которая асинхронно генерирует значения в зависимости от переданного входного параметра. В качестве примера, функция values может выглядеть так:

async function* values(input) {
  await new Promise(resolve => setTimeout(resolve, 1000 - input * 100)); // Задержка перед первым значением
  yield input;
  await new Promise(resolve => setTimeout(resolve, input * 200)); // Задержка перед вторым значением
  yield input + 10;
}

Создание обертки для множества генераторов

Теперь давайте создадим функцию Generator_any, которая будет принимать массив генераторов и будет возвращать значение по мере его готовности:

async function awaitNext(generator, index) {
  const next = await generator.next();
  return { index, next };
}

async function* Generator_any(generators) {
  let running = generators.length;
  const nexts = generators.map(awaitNext); // Массив обещаний

  while (true) {
    const { index, next } = await Promise.any(nexts); // Получаем первое завершенное обещание
    if (next.done) {
      nexts[index] = new Promise(() => {}); // Убираем завершенное обещание
      if (--running === 0) return; // Все обещания завершены
    } else {
      nexts[index] = awaitNext(generators[index], index); // Обновляем обещание для следующего значения
      yield next.value; // Возвращаем значение
    }
  }
}

Использование функции

Теперь мы можем использовать наше новое решение в асинхронной функции:

(async function () {
  const inputs = [1, 2, 3, 4, 5];
  const generators = inputs.map(input => values(input)); // Создаем массив генераторов
  for await (const i of Generator_any(generators)) {
    console.log(i); // Печатаем каждое полученное значение
  }
})();

Объяснение работы

  1. Массив промисов (nexts): Каждый элемент этого массива является обещанием, которое будет разрешено, когда соответствующий генератор выдаст значение.
  2. Цикл while (true): С помощью Promise.any мы ждем, пока какое-то из обещаний не будет выполнено. Как только это происходит, мы обновляем соответсвующий элемент в массиве nexts или удаляем его, если генератор завершил свою работу.
  3. Завершение работы: Когда все генераторы завершены, цикл завершается.

Это решение позволяет эффективно обрабатывать несколько асинхронных генераторов и возвращать их значения по мере готовности, без необходимости дожидаться завершения работы каждого из них.

Оцените материал
Добавить комментарий

Капча загружается...