¿Es un anti-patrón usar async / await dentro de un nuevo constructor Promise ()?

92

Estoy usando la async.eachLimitfunción para controlar el número máximo de operaciones a la 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 puede ver, no puedo declarar la myFunctionfunción como asincrónica porque no tengo acceso al valor dentro de la segunda devolución de llamada de la eachLimitfunción.

Alexis Tyler
fuente
"Como puede ver, no puedo declarar myFunction como asíncrono" --- ¿puede dar más detalles?
zerkms
1
Oh, ok ... lo siento. Necesito el constructor porque necesito async.eachLimit para evitar más de 500 operaciones asincrónicas a la vez. Estoy descargando y extrayendo datos de archivos de texto y quiero evitar muchas operaciones asincrónicas, después de extraer los datos, debo devolver una Promesa con los datos y no podré devolverla desde la devolución de llamada del async.eachLimit .
1. ¿Por qué necesitas la espera? Async ya es un mecanismo de control de flujo. 2. Si desea utilizar async.js con promesas dentro de node.js, eche un vistazo a async-q
slebetman
Para evitar el infierno de devolución de llamada, y si algo arroja, la promesa externa se capturaría.

Respuestas:

81

Está utilizando efectivamente promesas dentro de la función ejecutora del constructor de promesas, por lo que este es el anti-patrón del constructor de promesas .

Su código es un buen ejemplo del riesgo principal: no propagar todos los errores de forma segura. Lea por qué hay .

Además, el uso de async/ awaitpuede hacer que las mismas trampas sean aún más sorprendentes. Comparar:

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

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

con un asyncequivalente ingenuo (incorrecto) :

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

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

Busque en la consola web de su navegador el último.

El primero funciona porque cualquier excepción inmediata en una función ejecutora del constructor de Promise rechaza convenientemente la promesa recién construida (pero dentro de cualquiera, .thenusted está solo).

El segundo no funciona porque cualquier excepción inmediata en una asyncfunción rechaza la promesa implícita devuelta por la asyncfunción misma .

Dado que el valor de retorno de una función ejecutora del constructor de promesas no se utiliza, ¡son malas noticias!

Tu codigo

No hay ninguna razón por la que no pueda 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.
    });
  });
}

Aunque, ¿por qué usar bibliotecas de control de simultaneidad obsoletas cuando las tiene await?

foque
fuente
12
No necesitas return await: return new Promisees suficiente.
lonesomeday
2
Apruebo oficialmente esta respuesta, habría dicho exactamente lo mismo :-)
Bergi
1
@celoxxx Echa un vistazo aquí . De hecho, nunca debe usar async.js con promesas
Bergi
1
@celoxxx Simplemente suelte los tipos y se convertirá en js simple. No debe usar async.js porque las diferentes interfaces (devoluciones de llamada de estilo de nodo frente a promesas) causan demasiada fricción y conducen a un código innecesario complicado y propenso a errores.
Bergi
1
Estoy de acuerdo contigo ... Pero este código es antiguo, y estoy refactorizando para usar events + async.js (para controlar el límite de async, todavía. Si conoces una forma mejor, dilo).
16

Estoy de acuerdo con las respuestas dadas anteriormente y aún así, a veces es mejor tener async dentro de su promesa, especialmente si desea encadenar varias operaciones que devuelven promesas y evitar el then().then()infierno. Consideraría usar algo como esto en esa situación:

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. La función pasada al Promiseconstructor no es asincrónica, por lo que los linters no muestran errores.
  2. Todas las funciones asíncronas se pueden llamar en orden secuencial usando await.
  3. Se pueden agregar errores personalizados para validar los resultados de las operaciones asíncronas
  4. El error se detecta muy bien eventualmente.

Sin embargo, un inconveniente es que debes recordar ponerlo try/catchy sujetarlo reject.

Vladyslav Zavalykhatko
fuente
4
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 y async resolverá este problema. porque ha aplicado el objeto Promise, es suficiente.

Alain
fuente
Entonces, en su ejemplo, ¿ axios.get(url)funcionará como si se llamara como await axios.get(url)?
PrestonDocks