Home > OS >  How to read from multiple Async JavaScript generators at the same time
How to read from multiple Async JavaScript generators at the same time

Time:08-01

I have a code like this:

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);
})();

which produce:

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

What addjustments I should make, to read from 2 generators at the same time, and get:

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

CodePudding user response:

In a comment you said:

I get 2 generators as output from libary which run a binary. One generator is with output (lines of texts, each line yielded at a time), and another generator contains information about progress (numbers from 0 to 100). Of course I want to be able to display progress simultaneously while displaying output.

In that case, loop through the async generators in parallel:¹

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]);
})();

But, note that if you do that with the specific two synthetic generators in your question, the output won't be exactly what you've said you wanted, because foo writes 1 synchronously before waiting 100ms, and similarly bar writes a synchronously before waiting 200ms, so the results start with 1 a 2 b and then continue with 3 4 c 5 6 d 7 8 e and so on. They are interleaved as you've said you wanted, just those synthetic ones don't quite have the timing you were expecting when writing the question.

Live Example:

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;
}


¹ "Parallel" in async terms. They don't literally run in parallel threads, they interleave (sort of like "cooperative multitasking"). JavaScript restricts its execution semantics to only a single active thread within a given realm ("realm" loosely means global environment and the code in it).

CodePudding user response:

Similar to TJ's functional approach, you can wrap each loop in an IIFE:

edit note: Originally this answer included promises, hence TJ's comments.

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);
})();

CodePudding user response:

Here's a way you can consume them as a single async generator, by using Promise.race on each iteration:

async function* race(iterable) {
  const generators = [...iterable];
  const sentinel = Promise.race([]);
  const counted = async (gen, index) => {
    const result = await gen.next();
    return [index, result];
  };
  const promises = generators.map(counted);

  while (promises.some(promise => promise !== sentinel)) {
    const [index, { done, value }] = await Promise.race(promises);

    if (done) {
      promises[index] = sentinel;
    } else {
      promises[index] = counted(generators[index], index);
      yield value;
    }
  }
}

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

function delay(ms) {
  return 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';
}

CodePudding user response:

I feasibly do not think that's possible while in JavaScript engine especially using async functions because the interpreter reads in ordered manner, when you have two console.log() functions the first console.log() function is first executed before the other that's why the numbers are printed before the letters.

  • Related