Como “multicast” um iterável assíncrono?

Aug 23 2020

Um asyncgerador pode ser de alguma forma broadcast ou multicast, de modo que todos os seus iteradores ("consumidores"? Assinantes?) Recebam todos os valores?

Considere este exemplo:

const fetchMock = () => "Example. Imagine real fetch";
async function* gen() {
  for (let i = 1; i <= 6; i++) {
    const res = await fetchMock();
    yield res.slice(0, 2) + i;
  }
}
const ait = gen();

(async() => {
  // first "consumer"
  for await (const e of ait) console.log('e', e);
})();
(async() => {
  // second...
  for await (const é of ait) console.log('é', é);
})();

As iterações "consomem" um valor, portanto, apenas uma ou outra o obtém. Eu gostaria que ambos (e quaisquer outros posteriores) obtivessem todos os yieldvalores de ed, se tal gerador for possível criar de alguma forma. (Semelhante a um Observable.)

Respostas

3 Bergi Aug 24 2020 at 01:35

Isso não é facilmente possível. Você precisará fazer o tee explicitamente . Isso é semelhante à situação para iteradores síncronos , apenas um pouco mais complicado:

const AsyncIteratorProto = Object.getPrototypeOf(Object.getPrototypeOf(async function*(){}.prototype));
function teeAsync(iterable) {
    const iterator = iterable[Symbol.asyncIterator]();
    const buffers = [[], []];
    function makeIterator(buffer, i) {
        return Object.assign(Object.create(AsyncIteratorProto), {
            next() {
                if (!buffer) return Promise.resolve({done: true, value: undefined});
                if (buffer.length) return buffer.shift();
                const res = iterator.next();
                if (buffers[i^1]) buffers[i^1].push(res);
                return res;
            },
            async return() {
                if (buffer) {
                    buffer = buffers[i] = null;
                    if (!buffers[i^1]) await iterator.return();
                }
                return {done: true, value: undefined};
            },
        });
    }
    return buffers.map(makeIterator);
}

Você deve garantir que ambos os iteradores sejam consumidos aproximadamente na mesma taxa para que o buffer não cresça muito.

xianshenglu Aug 23 2020 at 12:30

Você quis dizer isso?

async function* gen() {
  for (let i = 1; i <= 6; i++) yield i;
}
//const ait = gen();


(async() => {
  // first iteration
  const ait = gen()
  for await (const e of ait) console.log(1, e);
})();
(async() => {
  // second...
  const ait = gen()
  for await (const é of ait) console.log(2, é);
})();