Iteradores Assíncronos - JavaScript

Iteradores assíncronos combinam as capacidades de iteradores com os operadores async e await. Eles são projetados principalmente para acessar fontes de dados que utilizam APIs assíncronas. Isso inclui dados carregados em partes, como da rede, do sistema de arquivos ou de um banco de dados.

Do tema sobre iteradores, sabemos que um iterador fornece o método next(), que retorna um objeto com duas propriedades: { value, done }. A propriedade value contém um valor, que pode ser obtido em um loop for..of ao iterar sobre o objeto. A propriedade done indica se a iteração foi concluída. Se done for false, significa que o iterador ainda não terminou e há mais objetos disponíveis. Se done for true, a iteração está concluída e não há mais objetos para iterar.

Um iterador assíncrono é semelhante a um iterador síncrono, exceto que seu método next() retorna um objeto Promise. A Promise retorna um objeto { value, done }.

Loop for-await-of

Para obter dados usando iteradores assíncronos, usamos o loop for-await-of:

for await (variable of iterable) {
    // ações
}

No loop for-await-of, após o operador of vem um conjunto de dados que pode ser iterado. Isso pode ser uma fonte de dados assíncrona, mas também pode ser uma fonte de dados síncrona, como arrays ou objetos embutidos como String, Map, Set, etc.

Vale notar que esta forma de loop só pode ser usada em funções definidas com o operador async.

Vejamos um exemplo simples onde a fonte de dados é um array comum:

const dataSource = ["Tom", "Sam", "Bob"];
async function readData() {
    for await (const item of dataSource) {
        console.log(item);
    }
}
readData();
// Tom
// Sam
// Bob

Aqui, o array dataSource é iterado. Durante o loop, para a fonte de dados (neste caso, o array), um iterador assíncrono é criado implicitamente usando o método [Symbol.asyncIterator](). Cada vez que acessamos um elemento, um objeto Promise é retornado pelo iterador, e obtemos o elemento atual do array.

Criando um Iterador Assíncrono

No exemplo acima, o iterador assíncrono foi criado implicitamente. No entanto, podemos defini-lo explicitamente. Por exemplo, vamos definir um iterador assíncrono que retorna elementos de um array:

const generatePerson = {
  [Symbol.asyncIterator]() {
      return {
      index: 0,
      people: ["Tom", "Sam", "Bob"],
      next() {
          if (this.index < this.people.length) {
          return Promise.resolve({ value: this.people[this.index++], done: false });
          }
          return Promise.resolve({ done: true });
      }
      };
  }
};

Aqui, definimos o objeto generatePerson, que possui o método [Symbol.asyncIterator](), representando o iterador assíncrono. A implementação permite que o objeto generatePerson seja iterável.

Pontos principais de um iterador assíncrono:

  • Um iterador assíncrono é implementado com o método [Symbol.asyncIterator]() que retorna um objeto.

  • O objeto retornado pelo iterador possui o método next() que retorna um objeto Promise.

  • O objeto Promise retorna um objeto com duas propriedades { value, done }. A propriedade value contém um valor, enquanto done indica se a iteração foi concluída. Se done for true, não há mais objetos para iterar, e value não precisa ser especificado.

No exemplo, o iterador retorna usuários sequencialmente. Para armazenar os usuários, usamos um array people e uma variável index para o índice atual.

index: 0,
people: ["Tom", "Sam", "Bob"],

No método next(), retornamos um objeto Promise. Se o índice atual for menor que o tamanho do array, retornamos um Promise com o elemento do array no índice atual:

return Promise.resolve({ value: this.people[this.index++], done: false });

Se todos os elementos do array foram obtidos, retornamos um Promise com { done: true }:

Isso indica ao código externo que todos os valores do iterador já foram obtidos.

Agora, vejamos como obter dados do iterador:

Podemos acessar o próprio iterador assíncrono:

generatePerson[Symbol.asyncIterator](); // obtenha o iterador assíncrono

E chamar explicitamente seu método next():

generatePerson[Symbol.asyncIterator]().next(); // Promise

Este método retorna uma Promise, na qual podemos chamar o método then() e processar seu valor:

generatePerson[Symbol.asyncIterator]()
    .next()
    .then((data) => console.log(data));    // {value: "Tom", done: false}

O objeto retornado pela Promise possui as propriedades value e done. Podemos acessar o valor diretamente:

generatePerson[Symbol.asyncIterator]()
    .next()
    .then((data) => console.log(data.value));  // Tom

Como o método next() retorna uma Promise, podemos usar o operador await para obter os valores:

async function printPeople() {
    const peopleIterator = generatePerson[Symbol.asyncIterator]();
    let personData;
    while(!(personData = await peopleIterator.next()).done) {
        console.log(personData.value);
    }
}
printPeople();

Aqui, em uma função assíncrona com um loop while, usamos await para obter sequencialmente os objetos Promise do iterador, extraindo dados até alcançar o fim.

No entanto, para iterar sobre um objeto de iterador assíncrono, é mais simples usar o loop for-await-of:

const generatePerson = {
  people: ["Tom", "Sam", "Bob"],
  [Symbol.asyncIterator]() {
    let index = 0;
    return {
      next: async () => {
        if (index < this.people.length) {
          // Simulando um atraso assíncrono de 2 segundos para cada pessoa
          const value = await new Promise(resolve => 
            setTimeout(() => resolve(this.people[index++]), 2000)
          );
          return { value, done: false };
        }
        return { done: true };
      }
    };
  }
};

async function printPeople() {
  for await (const person of generatePerson) {
    console.log(person);
  }
}

printPeople();

Como o objeto generatePerson implementa o método [Symbol.asyncIterator](), podemos iterar sobre ele com o loop for-await-of. Em cada iteração, o método next() retorna uma Promise com o próximo elemento do array people. O resultado será:

Tom
Sam
Bob

Vale ressaltar que não podemos usar um loop for-of comum para iterar sobre um objeto com um iterador assíncrono.

Outro exemplo simples é a obtenção de números:

const generateNumber = {
  [Symbol.asyncIterator]() {
      return {
      current: 0,
      end: 10,
      next() {
          if (this.current <= this.end) {
          return Promise.resolve({ value: this.current++, done: false });
          }
          return Promise.resolve({ done: true });
      }
    };
  }
};
async function printNumbers(){
    for await (const n of generateNumber) {
        console.log(n);
    }
}
printNumbers();

Neste exemplo, o iterador assíncrono do objeto generateNumber retorna números de 0 a 10.

Conclusão

Iteradores assíncronos são uma maneira eficaz de acessar fontes de dados assíncronas. Eles combinam as capacidades de iteradores com os operadores async e await. Isso permite que você acesse dados carregados em partes, como da rede, do sistema de arquivos ou de um banco de dados.

Política de Privacidade

Copyright © www.programicio.com Todos os direitos reservados

É proibida a reprodução do conteúdo desta página sem autorização prévia do autor.

Contato: programicio@gmail.com