如何同时从多个异步 JavaScript 生成器读取

问题描述 投票:0回答:4

我有这样的代码:

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function* foo() {
    yield 1;
    await delay(100);
    yield 2;
    await delay(100);
    yield 3;
    await delay(100);
    yield 4;
    await delay(100);
    yield 5;
    await delay(100);
    yield 6;
    await delay(100);
    yield 7;
    await delay(100);
    yield 8;
    await delay(100);
    yield 9;
    await delay(100);
    yield 10;
}

async function* bar() {
    yield 'a';
    await delay(200);
    yield 'b';
    await delay(200);
    yield 'c';
    await delay(200);
    yield 'd';
    await delay(200);
    yield 'e';
}

(async function () {
    for await (const num of foo()) {
        console.log(num);
    }
    for await (const str of bar()) {
        console.log(str);
    }

    await delay(2000);
})();

产生:

1
2
3
4
5
6
7
8
9
10
a
b
c
d
e

我应该做哪些调整,同时从 2 个生成器读取数据,并得到:

1
2
a
3
4
b
5
6
c
7
8
d
9
10
e
javascript generator
4个回答
4
投票

在评论中你说:

我从运行二进制文件的库中得到 2 个生成器作为输出。一个生成器带有输出(文本行,一次生成每一行),另一个生成器包含有关进度的信息(从 0 到 100 的数字)。当然我希望能够在显示输出的同时显示进度。

在这种情况下,并行循环异步生成器:¹

async function show(g) {
    for await (const value of g) {
        console.log(value);
    }
}
(async function () {
    // Start processing the first one
    const pFoo = show(foo());
    // Start processing the second one
    const pBar = show(bar());
    // Wait until both are done
    await Promise.all([pFoo, pBar]);
})();

但是,请注意,如果您使用问题中的特定两个合成生成器执行此操作,则输出将不会完全符合您所说的要求,因为

foo
在等待 100 毫秒之前会写入
1
同步 ,类似地,
bar
在等待200ms之前写入
a
同步,因此结果以
1 a 2 b
开始,然后继续
3 4 c 5 6 d 7 8 e
,依此类推。正如您所说,它们交错的,只是那些合成的不太符合您在编写问题时所期望的时间。

实例:

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function* foo() {
    yield 1;
    await delay(100);
    yield 2;
    await delay(100);
    yield 3;
    await delay(100);
    yield 4;
    await delay(100);
    yield 5;
    await delay(100);
    yield 6;
    await delay(100);
    yield 7;
    await delay(100);
    yield 8;
    await delay(100);
    yield 9;
    await delay(100);
    yield 10;
}

async function* bar() {
    yield "a";
    await delay(200);
    yield "b";
    await delay(200);
    yield "c";
    await delay(200);
    yield "d";
    await delay(200);
    yield "e";
}

async function show(g) {
    for await (const value of g) {
        console.log(value);
    }
}
(async function () {
    const pFoo = show(foo());
    const pBar = show(bar());
    await Promise.all([pFoo, pBar]);
})();
.as-console-wrapper {
    max-height: 100% !important;
}


¹

async
术语中的“并行”。它们实际上并不是在并行线程中运行,而是交错运行(有点像“协作多任务处理”)。 JavaScript 将其执行语义限制为给定领域内的单个活动线程(“领域”宽松地表示全局环境及其中的代码)。


2
投票

这里有一种方法可以将它们作为单个异步生成器使用,方法是在每次迭代中使用

Promise.race

async function* race([...generators]) {
  const next = generator => {
    const promise = generator.next().then(
      ({ done, value }) => ({ done, value, generator, promise }),
    );
    return promise;
  };
  const promises = generators.reduce(
    (set, gen) => set.add(next(gen)),
    new Set(),
  );

  while (promises.size > 0) {
    const { done, value, generator, promise } = await Promise.race(promises);
    promises.delete(promise);

    if (!done) {
      promises.add(next(generator));
      yield value;
    }
  }
}

(async () => {
  for await (const value of race([foo(), bar()])) {
    console.log(value);
  }
})();

async function* foo() {
  yield 1;
  await delay(100);
  yield 2;
  await delay(100);
  yield 3;
  await delay(100);
  yield 4;
  await delay(100);
  yield 5;
  await delay(100);
  yield 6;
  await delay(100);
  yield 7;
  await delay(100);
  yield 8;
  await delay(100);
  yield 9;
  await delay(100);
  yield 10;
}

async function* bar() {
  yield 'a';
  await delay(200);
  yield 'b';
  await delay(200);
  yield 'c';
  await delay(200);
  yield 'd';
  await delay(200);
  yield 'e';
}

function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}


0
投票

与 TJ 的函数方法类似,您可以将每个循环包装在 IIFE 中:

编辑注释:最初这个答案包含承诺,因此 TJ 的评论。

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function* foo() {
    yield 1;
    await delay(100);
    yield 2;
    await delay(100);
    yield 3;
    await delay(100);
    yield 4;
    await delay(100);
    yield 5;
    await delay(100);
    yield 6;
    await delay(100);
    yield 7;
    await delay(100);
    yield 8;
    await delay(100);
    yield 9;
    await delay(100);
    yield 10;
}

async function* bar() {
    yield 'a';
    await delay(200);
    yield 'b';
    await delay(200);
    yield 'c';
    await delay(200);
    yield 'd';
    await delay(200);
    yield 'e';
}

(async function () {
    
    const numPromise = (async () => {
      for await (const num of foo()) {
            console.log(num);
      }
    })();
    
    const strPromise = (async () => {
      for await (const str of bar()){
        console.log(str);
      }
    })();
    
    await Promise.all([numPromise,strPromise])
    console.log('Done!')

      
    await delay(2000);
})();


-2
投票

我认为在 JavaScript 引擎中这是不可能的,尤其是使用异步函数,因为解释器以有序的方式读取,当你有两个 console.log() 函数时,第一个 console.log() 函数首先在另一个函数之前执行,这就是为什么数字打印在字母之前。

© www.soinside.com 2019 - 2024. All rights reserved.