É um antipadrão usar async / await dentro de um novo construtor Promise ()?

103

Estou usando a async.eachLimitfunção para controlar o número máximo de operações por vez.

const { eachLimit } = require("async");

function myFunction() {
 return new Promise(async (resolve, reject) => {
   eachLimit((await getAsyncArray), 500, (item, callback) => {
     // do other things that use native promises.
   }, (error) => {
     if (error) return reject(error);
     // resolve here passing the next value.
   });
 });
}

Como você pode ver, não posso declarar a myFunctionfunção como assíncrona porque não tenho acesso ao valor dentro do segundo retorno de chamada da eachLimitfunção.

Alexis Tyler
fonte
"Como você pode ver, eu não posso declarar myFunction como assíncrono" --- você pode explicar mais?
zerkms
1
Oh está bem, desculpa. Preciso do construtor porque preciso do async.eachLimit para evitar mais de 500 operações assíncronas por vez. Estou baixando e extraindo dados de arquivos de texto e quero evitar muitas operações assíncronas, depois de extrair os dados, devo retornar uma promessa com os dados e não poderei retorná-la do retorno de chamada do async.eachLimit .
1. Por que você precisa de esperar? Async já é um mecanismo de controle de fluxo. 2. Se você quiser usar async.js com promessas dentro de node.js, dê uma olhada em async-q
slebetman
Para evitar o inferno de callback, e se algo acontecer, a promessa externa pegará.

Respostas:

88

Você está efetivamente usando promessas dentro da função do executor do construtor de promessa, então este é o antipadrão do construtor de Promise .

Seu código é um bom exemplo do principal risco: não propagar todos os erros com segurança. Leia porque .

Além disso, o uso de async/ awaitpode tornar as mesmas armadilhas ainda mais surpreendentes. Comparar:

let p = new Promise(resolve => {
  ""(); // TypeError
  resolve();
});

(async () => {
  await p;
})().catch(e => console.log("Caught: " + e)); // Catches it.

com um asyncequivalente ingênuo (errado) :

let p = new Promise(async resolve => {
  ""(); // TypeError
  resolve();
});

(async () => {
  await p;
})().catch(e => console.log("Caught: " + e)); // Doesn't catch it!

Procure o último no console da web do seu navegador.

O primeiro funciona porque qualquer exceção imediata em uma função executora do construtor Promise convenientemente rejeita a promessa recém-construída (mas dentro de qualquer .thenvocê está por conta própria).

O segundo não funciona porque qualquer exceção imediata em uma asyncfunção rejeita a promessa implícita retornada pela asyncprópria função .

Como o valor de retorno de uma função executora do construtor de promessa não é usado, isso é uma má notícia!

Seu código

Não há nenhuma razão que você não possa definir myFunctioncomo async:

async function myFunction() {
  let array = await getAsyncArray();
  return new Promise((resolve, reject) => {
    eachLimit(array, 500, (item, callback) => {
      // do other things that use native promises.
    }, error => {
      if (error) return reject(error);
      // resolve here passing the next value.
    });
  });
}

Porém, por que usar bibliotecas de controle de simultaneidade desatualizadas quando você tem await?

bujarrona
fonte
12
Você não precisa return await: return new Promiseé suficiente.
solitário dia
2
Eu aprovo oficialmente esta resposta, eu teria dito exatamente o mesmo :-)
Bergi
1
@celoxxx Dê uma olhada aqui . Na verdade, você nunca deve usar async.js com promessas
Bergi,
1
@celoxxx Simplesmente elimine os tipos e ele se torna simples js. Você não deve usar o async.js porque as diferentes interfaces - retornos de chamada de estilo de nó vs promessas - causam muito atrito e levam a códigos complicados e sujeitos a erros desnecessários.
Bergi
1
Concordo com você ... Mas esse código é antigo, e estou refatorando para usar events + async.js (para controlar o limite de async, ainda. Se você conhece uma maneira melhor, por favor diga).
21

Eu concordo com as respostas dadas acima e ainda assim, às vezes é mais legal ter assíncrono dentro de sua promessa, especialmente se você quiser encadear várias operações retornando promessas e evitar o then().then()inferno. Eu consideraria usar algo assim nessa situação:

const operation1 = Promise.resolve(5)
const operation2 = Promise.resolve(15)
const publishResult = () => Promise.reject(`Can't publish`)

let p = new Promise((resolve, reject) => {
  (async () => {
    try {
      const op1 = await operation1;
      const op2 = await operation2;

      if (op2 == null) {
         throw new Error('Validation error');
      }

      const res = op1 + op2;
      const result = await publishResult(res);
      resolve(result)
    } catch (err) {
      reject(err)
    }
  })()
});

(async () => {
  await p;
})().catch(e => console.log("Caught: " + e));
  1. A função passada ao Promiseconstrutor não é assíncrona, portanto, os linters não mostram erros.
  2. Todas as funções assíncronas podem ser chamadas em ordem sequencial usando await.
  3. Erros personalizados podem ser adicionados para validar os resultados das operações assíncronas
  4. O erro é detectado bem eventualmente.

Uma desvantagem, porém, é que você deve se lembrar de colocá try/catch-lo e prendê-lo reject.

Vladyslav Zavalykhatko
fonte
3
static getPosts(){
    return new Promise( (resolve, reject) =>{
        try {
            const res =  axios.get(url);
            const data = res.data;
            resolve(
                data.map(post => ({
                    ...post,
                    createdAt: new Date(post.createdAt)
                }))
            )
        } catch (err) {
            reject(err);                
        }
    })
}

remove await e async resolverá esse problema. porque você aplicou o objeto Promise, isso é o suficiente.

Alain
fonte
1
Portanto, em seu exemplo, axios.get(url)funcionará como se fosse chamado de await axios.get(url)?
PrestonDocks,