JavaScript: Funções Generator Assíncronas

Image for post
Image for post

A proposta do TC39 de iteradores assíncronos que trouxe for/await/of para o JavaScript também introduziu o conceito de uma função generator assíncrona . Agora, o JavaScript tem 6 tipos distintos de funções:

  • Funções normais function() {}
  • Funções de seta () => {}
  • Funções assíncronas 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. Você pode pensar em um iterador assíncrono como um iterador 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 .

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.

'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.

'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 .

Suponha que você deseja percorrer todos os documentos em um cursor Mongoose e relatar o progresso via websocket ou para a linha de comando.

'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 . 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.

'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.

(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 , 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

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store