Async: Trate las funciones sin devolución de llamada como promesas

Creado en 9 jul. 2019  ·  12Comentarios  ·  Fuente: caolan/async

El comportamiento actual es muy sorprendente:

await async.parallel({foo: async()=>Promise.all([1,2])})
// { foo: [ 1, 2 ] }
await async.parallel({foo: ()=>Promise.all([1,2])})
// waits forever because non-async function expects a ballback

Porque para funciones cortas, es tentador escribir foo: () => doSomething() . Es muy fácil olvidar la palabra clave async allí

Pero actualmente esto no funcionará, debe escribirse como foo: async () => doSomething()

¿Podríamos comprobar si se pasa una devolución de llamada o no, en lugar de comprobar si la función es una función AsyncFunction?

wont fix

Comentario más útil

Incluso con este problema cerrado, a partir del problema vinculado anterior, me gustaría volver a enfatizar las declaraciones de @caub aquí.
El comportamiento actual me confundió mucho, y aunque (en cierto modo) entiendo las limitaciones técnicas que se comentan aquí, creo que la documentación simplemente no está en consonancia con el comportamiento actual.
Por ejemplo, mapLimit dice "Devoluciones: una promesa, si no se pasa ninguna devolución de llamada". Comencé con la versión de devolución de llamada, y luego básicamente omití la devolución de llamada y me sorprendió que no recibiera la Promesa (porque no tenía la palabra clave 'async').
Además, la declaración de 'devolución de llamada' en la función iteratee no es necesaria, en su lugar, debe 'devolver' un valor.
Entonces, el comportamiento debe cambiarse (preferido) o al menos la documentación y los ejemplos deben alinearse en mi punto de vista para evitar confusiones.

Todos 12 comentarios

No tenemos forma de poder detectar de manera confiable si una función devuelve una promesa o no, y si necesitamos pasarle una devolución de llamada o no de antemano. ( Function.length o analizar Function.toString() no es lo suficientemente bueno) De esto se trata asyncify : convertir funciones de devolución de promesa en funciones que reciben una devolución de llamada. Como podemos detectar funciones async ya que tienen un tipo diferente, podemos automáticamente asyncify ellas (que es como funciona esto internamente). Pero para las funciones ordinarias que devuelven una promesa, no tenemos forma de saberlo antes de que Async las llame.

@aearly hmm, ¿tal vez algo más simple como detectar la devolución de llamada en el nivel superior?

async function parallel(o, cb) {
  if (!cb) { // promise behavior
    return Object.fromEntries(
      await Promise.all(Object.entries(o).map(async ([k, v]) => [k, await v()]))
    )
  }
  // else do the callback behavior
}

Ya tenemos algo así: devuelve una promesa si omites la devolución de llamada a async.parallel . No asumimos que las funciones que le pasas devuelven promesas si omites la devolución de llamada porque no tenemos forma de asumir la intención del usuario. ¿Quizás estoy entendiendo mal lo que quieres decir?

Ok, seamos concretos: https://repl.it/@caub/async -async

// npm.im/async expects all inner functions to be async,
// even when using no outer callback (as 2nd arg of async.parallel)
console.log(
  await async.parallel({
    foo: async () => delay(1000).then(() => 1),
    bar: async (rs) => {
      return 2
    },
  })
);

// Doesn't work without those async,
// but I'd like this to resolve just like above instead of freezing
console.log(
  await async.parallel({
    foo: () => delay(1000).then(() => 1),
    bar: () => {
      return 2
    },
  })
);

Creo que, en una próxima versión principal futura, podría ser interesante admitir solo la devolución de llamada de una manera similar a esta:

await Promise.all([
  { then(resolve){ setTimeout(resolve, 50, 1) } }, // callback way
  2,
  delay(45).then(())=>3)
])
// [1, 2, 3]

eliminando la necesidad de async palabras clave innecesarias en las funciones internas

La ventaja es más claridad. Entiendo su punto de no asumir la intención del usuario, pero aún asume que las personas no olvidarán la palabra clave async cuando quieran usar la resolución de Promise

Eso rompería la compatibilidad con versiones anteriores de una manera bastante grande; por ejemplo, ya no podría hacer cosas como Async.map(fileNames, fs.readFile) .

¿Podrías explicar dónde se rompería?

Lo que actualmente se rompe es:

await async.map(['server.js', 'package.json'], fs.readFile) // works
await async.map(['server.js', 'package.json'], fs.promises.readFile) // works
await async.map(['server.js', 'package.json'], s => fs.promises.readFile(s)) // doesn't work
await async.map(['server.js', 'package.json'], async s => fs.promises.readFile(s)) // works

La idea es arreglar el tercero.

Tengo esos problemas constantemente, por ejemplo, escribí:

  // ...
  doc = await async.map(items, item => item.toDoc(currentUser, options));

en algún lugar, y nuevamente, tiempo de espera porque me faltaba un async item => item.toDoc..

Si estoy solo en ese caso, no vale la pena, pero si hay más personas en esta situación, creo que vale la pena considerar el cambio propuesto.

Este ejemplo con async.map también es muy representativo, ya que puedes ver mi punto con cómo

  doc = await Promise.all(items.map(item => item.toDoc(currentUser, options)));

funciona sin la necesidad de un async item => item.toDoc..

No hay forma de arreglar de manera confiable el tercero, esto se ha discutido mucho en el pasado. No podemos verificar el valor de retorno de una función y luego retroactivamente no pasar una devolución de llamada a la función. La palabra clave async es tan buena como parece.

@caub Shameless se conecta aquí, pero creo que asyncp tiene la capacidad de manejar lo que estás buscando aquí.

Incluso con este problema cerrado, a partir del problema vinculado anterior, me gustaría volver a enfatizar las declaraciones de @caub aquí.
El comportamiento actual me confundió mucho, y aunque (en cierto modo) entiendo las limitaciones técnicas que se comentan aquí, creo que la documentación simplemente no está en consonancia con el comportamiento actual.
Por ejemplo, mapLimit dice "Devoluciones: una promesa, si no se pasa ninguna devolución de llamada". Comencé con la versión de devolución de llamada, y luego básicamente omití la devolución de llamada y me sorprendió que no recibiera la Promesa (porque no tenía la palabra clave 'async').
Además, la declaración de 'devolución de llamada' en la función iteratee no es necesaria, en su lugar, debe 'devolver' un valor.
Entonces, el comportamiento debe cambiarse (preferido) o al menos la documentación y los ejemplos deben alinearse en mi punto de vista para evitar confusiones.

@caub Esta es la razón
https://github.com/caolan/async/blob/8aecf108b3922bc5211036706a0f6f75e02bd42b/lib/internal/wrapAsync.js#L3 : L5

function isAsync(fn) {
    return fn[Symbol.toStringTag] === 'AsyncFunction';
}

De esta forma, es más fácil comprobar si la función devuelve una promesa sin llamarla realmente. Pero aún estoy de acuerdo con usted en que esto va en contra de las convenciones async / await. Ya que agregar una palabra clave async sin una await interna se considera una mala práctica en la mayoría de los casos.

de https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

Se puede pensar que el cuerpo de una función async está dividido por cero o más expresiones de espera. El código de nivel superior, incluida la primera expresión de espera (si hay una), se ejecuta de forma sincrónica.

... y, por lo tanto, será inútil y, lo peor, agregará algo de rendimiento si babel transpila el código, por ejemplo, ya que agregará más código y pasos solo por la existencia de la palabra clave async

Por ejemplo, lo siguiente:

async function foo() {
   return 1
}

...es equivalente a:

function foo() {
   return Promise.resolve(1)
}

pero con esta biblioteca,

async.mapLimit([1], 1, async v => v)

y

async.mapLimit([1], 1, v => Promise.resolve(v)))

no son equivalentes!

¿Fue útil esta página
0 / 5 - 0 calificaciones