JavaScript: Funções Generator Assíncronas
Tags: ptbr, javascript, node, webdev
Published: 2020-10-14 20:41:28

A [proposta do TC39 de iteradores assíncronos](https://github.com/tc39/proposal-async-iteration) que trouxe [`for/await/of` para o JavaScript](http://thecodebarbarian.com/getting-started-with-async-iterators-in-node-js) também introduziu o conceito de uma [função generator assíncrona](https://github.com/tc39/proposal-async-iteration#async-generator-functions) . Agora, o JavaScript tem 6 tipos distintos de funções:

-   Funções normais `function() {}`
-   Funções de seta `() => {}`
-   [Funções assíncronas](http://thecodebarbarian.com/async-functions-in-javascript.html)  `async function() {}`
-   Funções de seta assíncrona `async () => {}`
-   Funções generator `function*() {}`
-   Funções generator assíncrona `async function*() {}`

As funções generator assíncronas são especiais porque você pode usar ambos `await` e `yield` em uma função generator assíncrona. As funções generator assíncrona diferem das funções assíncrona e funções generator porque não retornam uma promessa ou um iterador, mas sim um [iterador assíncrono](http://thecodebarbarian.com/getting-started-with-async-iterators-in-node-js#your-first-async-iterator). Você pode pensar em um iterador assíncrono como um [iterador](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators) onde a função `next()` sempre retorna uma promessa.

## Sua primeira função generator assíncrona

As funções generator assíncronas se comportam de maneira semelhante às funções generator: a função generator retorna um objeto que tem uma função `next()` e a chamada `next()` executa a função generator até o próximo `yield`. A diferença é que [a função `next()` de um iterador assíncrono retorna uma promessa](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of#Iterating_over_async_iterables) .

Abaixo está um exemplo "Hello, World" com funções generator assíncrona. Observe que o script a seguir não funcionará em versões Node.js anteriores a 10.x.

```javascript
'usze strict';

async function* run() {
  // Dorme por 100ms, see: https://masteringjs.io/tutorials/fundamentals/sleep
  await new Promise(resolve => setTimeout(resolve, 100));
  yield 'Hello';
  console.log('World');
}

// `run()` retorna um iterador assíncrono.
const asyncIterator = run();

// A função não é executada até `next()` ser chamado
asyncIterator.next().
  then(obj => console.log(obj.value)). // Prints "Hello"
  then(() => asyncIterator.next());  // Prints "World"
```

A maneira mais limpa de fazer um loop em todos os valores de uma função generator assíncrona é usando um `for/await/of`.

```javascript
'use strict';

async function* run() {
  await new Promise(resolve => setTimeout(resolve, 100));
  yield 'Hello';
  console.log('World');
}

const asyncIterator = run();

// Imprimi "Hello\nWorld"
(async () => {
  for await (const val of asyncIterator) {
    console.log(val); // Imprimi "Hello"
  }
})();
```

## Um caso de uso prático

Você pode estar pensando "por que o JavaScript precisa de funções generator assíncronas quando já tem funções assíncronas e funções generator?" Um caso de uso é o problema clássico da barra de progresso em que [Ryan Dahl originalmente escreveu Node.js para resolver](https://stackoverflow.com/questions/31529013/nodejs-file-upload-with-progress-bar-using-core-nodejs-and-the-original-node-s) .

Suponha que você deseja percorrer todos os documentos em um [cursor Mongoose](https://thecodebarbarian.com/cursors-in-mongoose-45) e relatar o progresso via [websocket](https://masteringjs.io/tutorials/node/websockets) ou para a linha de comando.

```javascript
'use strict';

const mongoose = require('mongoose');

async function* run() {
  await mongoose.connect('mongodb://localhost:27017/test', { useNewUrlParser: true });
  await mongoose.connection.dropDatabase();

  const Model = mongoose.model('Test', mongoose.Schema({ name: String }));
  for (let i = 0; i < 5; ++i) {
    await Model.create({ name: `doc ${i}` });
  }

  // Supondo que você tenha vários documentos e você quer reportar o progresso
  // de cada um. Você pode usar `yield` após processar cada documento.
  const total = 5;
  const cursor = Model.find().cursor();

  let processed = 0;
  for await (const doc of cursor) {
    // Você pode pensar em `yield` como reportando: "Finalizei uma unidade de trabalho"
    yield { processed: ++processed, total };
  }
}

(async () => {
  for await (const val of run()) {
    // Imprimi "1 / 5", "2 / 5", "3 / 5", etc.
    console.log(`${val.processed} / ${val.total}`);
  }
})();
```

As funções generator assíncronas tornam mais fácil para sua função assíncrona relatar seu progresso de uma forma [sem frameworks](https://www.getrevue.co/profile/masteringjs/issues/framework-free-javascript-why-it-matters-188138) . Não há necessidade de criar explicitamente um websocket ou log no console - você pode lidar com isso separadamente se assumir que sua lógica de negócios usa `yield` para relatar seu progresso.

## Com Observáveis

Os iteradores assíncronos são ótimos, mas há outra primitiva de simultaneidade com a qual as funções generator assíncronas se alinham bem: os [observáveis ​​RxJS](https://www.npmjs.com/package/rxjs).

```javascript
'use strict';

const { Observable } = require('rxjs');
const mongoose = require('mongoose');

async function* run() {
  // Mesmo código de antes
}

// Cria um observável que emite cada valor que o iterador assíncrono retorna
const observable = Observable.create(async (observer) => {
  for await (const val of run()) {
    observer.next(val);
  }
});

// Imprimi "1 / 5", "2 / 5", "3 / 5", etc.
observable.subscribe(val => console.log(`${val.processed} / ${val.total}`));
```

Existem duas diferenças principais entre usar um observável RxJS e um iterador assíncrono. Primeiro, no exemplo acima, o código que se conecta ao console `subscribe()` é _reativo,_ e não _imperativo_ . Em outras palavras, o manipulador `subscribe()` não tem como afetar o código no corpo da função assíncrona, ele simplesmente reage aos eventos. Ao usar um `for/await/of` loop, você pode, por exemplo, adicionar uma pausa de 1 segundo antes de retomar a função generator assíncrona.

```javascript
(async () => {
  for await (const val of run()) {
    // Imprimi "1 / 5", "2 / 5", "3 / 5", etc.
    console.log(`${val.processed} / ${val.total}`);
    // Adiciona 1 segundo de delay para cada instrução `yield`
    await new Promise(resolve => setTimeout(resolve, 1000));
  }
})();
```

A segunda é que, como os [observáveis ​​RxJS são frios por padrão](https://medium.com/codingthesmartway-com-blog/getting-started-with-rxjs-part-3-hot-and-cold-observables-4713757c9a88) , uma nova chamada `subscribe()` executa novamente a função.

# Finalizando

As funções generator assíncronas podem parecer de nicho e confusas no início, mas fornecem o que pode se tornar a solução nativa do JavaScript para problemas de "barra de progresso". Usar `yield` para relatar o progresso de uma função assíncrona é uma ideia atraente porque permite que você desacople sua lógica de negócios de sua estrutura de relatório de progresso. Dê uma chance aos generators assíncronos na próxima vez que precisar implementar uma barra de progresso.

---

# Créditos

- [Async Generator Functions in JavaScript](https://thecodebarbarian.com/async-generator-functions-in-javascript.html), escrito originalmente por [Valeri Karpov](https://mobile.twitter.com/code_barbarian).