Aguarde...

11 de agosto de 2019

Funções do gerador assíncrono em JavaScript

Funções do gerador assíncrono em JavaScript

A proposta dos iteradores async TC39 que trouxe for/await/ofpara o JavaScript também introduziu o conceito de uma função geradora 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 do gerador function*() {}
  • Funções do gerador assíncrono async function*() {}

As funções do gerador assíncrono são especiais porque você pode usar ambas await e yieldem uma função geradora assíncrona. As funções do gerador assíncrono diferem das funções assíncronas e das funções do gerador, pois 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 cuja next()função sempre retorna uma promessa.

Sua primeira função de gerador assíncrono

As funções do gerador assíncrono se comportam de maneira semelhante às funções do gerador: a função do gerador retorna um objeto que tem uma next()função e a chamada next()executa a função do gerador até a próxima yield. A diferença é que a next()função de um iterador assíncrono retorna uma promessa .

Abaixo está um exemplo “Hello, World” com funções geradoras assíncronas. Observe que o script abaixo não funcionará nas versões do Node.js antes do 10.x.

'use strict';

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

// `run()` returns an async iterator.
const asyncIterator = run();

// The function doesn't start running until you call `next()`
asyncIterator.next().
  then(obj => console.log(obj.value)). // Prints "Hello"
  then(() => asyncIterator.next());  // Prints "World"

A maneira mais limpa de percorrer toda uma função geradora assíncrona é usando um for/await/ofloop.

'use strict';

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

const asyncIterator = run();

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

Um caso prático de uso

Você pode estar pensando “por que o JavaScript precisa de funções geradoras assíncronas quando já possui funções assíncronas e funções geradoras?” Um caso de uso é o problema clássico barra de progresso que Ryan Dahl escreveu originalmente Node.js para resolver .

Suponha que você queira percorrer todos os documentos em um cursor do Mongoose e relatar o progresso via websocket ou à 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}` });
  }

  // Suppose you have a lot of documents and you want to report when you process
  // each one. You can `yield` after processing each individual doc.
  const total = 5;
  const cursor = Model.find().cursor();

  let processed = 0;
  for await (const doc of cursor) {
    // You can think of `yield` as reporting "I'm done with one unit of work"
    yield { processed: ++processed, total };
  }
}

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

As funções do gerador assíncrono facilitam para a sua função assíncrona reportar seu progresso de maneira livre de framework . Não há necessidade de criar explicitamente um websocket ou um log no console – você pode lidar com isso separadamente se assumir que sua lógica de negócios é usada yieldpara relatórios de andamento.

Com observáveis

Os iteradores assíncronos são ótimos, mas há outra primitiva de simultaneidade que as funções do gerador assíncrono alinham bem com: Observables RxJS .

'use strict';

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

async function* run() {
  // Same as before
}

// Create an observable that emits each value the async generator yields
// to subscribers.
const observable = Observable.create(async (observer) => {
  for await (const val of run()) {
    observer.next(val);
  }
});

// Prints "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 versus um iterador assíncrono. Primeiro, no exemplo acima, o código que registra no console subscribe()é reativo e não imperativo . Em outras palavras, o subscribe()manipulador 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/ofloop, você pode, por exemplo, adicionar uma pausa de 1 segundo antes de retomar a função do gerador assíncrono.

(async () => {
  for await (const val of run()) {
    // Prints "1 / 5", "2 / 5", "3 / 5", etc.
    console.log(`${val.processed} / ${val.total}`);
    // This adds a 1 second delay to every `yield` statement.
    await new Promise(resolve => setTimeout(resolve, 1000));
  }
})();

A segunda é que, como os observáveis ​​RxJS são frios por padrão , uma nova subscribe()chamada reexecutaa função.

// Prints "1 / 5", "2 / 5", "3 / 5", etc.
observable.subscribe(val => console.log(`${val.processed} / ${val.total}`));
// Kicks off a separate instance of `run()`
observable.subscribe(val => console.log(`${val.processed} / ${val.total}`));

Se movendo

As funções do gerador assíncrono podem parecer nus e confusas no início, mas fornecem o que pode se tornar a solução nativa do JavaScript para o problema da barra de progresso. Usar yieldpara relatar o progresso de uma função assíncrona é uma ideia atraente porque permite dissociar sua lógica de negócios de sua estrutura de relatório de progresso. Dê uma chance aos geradores assíncronos da próxima vez que você precisar implementar uma barra de progresso.

Procurando tornar-se fluente em assíncrono / aguardar? Meu novo ebook, Mastering Async / Await, foi desenvolvido para fornecer uma compreensão integrada dos fundamentos async / await e como async / wait se ajusta ao ecossistema de JavaScript em poucas horas.

Postado em BlogTags:
Escreva um comentário