10

Estoy intentando comprender como funciona await/async. He leido varias opiniones que vienen a decir

async/await no es mas que una syntax-sugar sobre las Promises.

Sin embargo, el resultado que estoy obteniendo choca frontalmente con esto: no soy capaz de explicar las salidas del siguiente código (tomado originalmente de aquí).

function sleep( ms ) {
  return new Promise( resolve => setTimeout( resolve, ms ) );
}

async function demo( ) {
  console.log( 'Taking a break...' );
  await sleep( 2000 );
  console.log( 'Two seconds later' );
}

async function demo2( ) {
  console.log( 'demo2 -> before' );
  await demo( );
  console.log( 'demo2 -> after' );
}

function demo3( ) {
  console.log( 'demo3 -> before' );
  demo2( );
  console.log( 'demo3 -> after' );
}

demo3( );

El resultado obtenido es:

demo3 -> before
demo2 -> before
Taking a break...
demo3 -> after
... pausa de 2 segundos ...
Two seconds later
demo2 -> after

Sin embargo, el resultado esperado es este otro:

demo3 -> before
demo2 -> before
Taking a break...
Two seconds later
demo2 -> after
demo3 -> after

De algún modo que no comprendo, el uso de async/await ¡ cambia el orden de las retornos ! No se me ocurre ninguna forma, usando solo callbacks y closures, que provoque ese comportamiento.

Por las salidas, parece que la función demo3( ) retorna antes que la función demo2( ), lo cual es sencillamente imposible según el modo de ejecución clásico que yo conozco.

¿ Como funciona realmente async/await ? ¿ Realmente es solo una facilidad para usar Promises ? ¿ Me estoy liando yo mismo buscando los 3 pies del gato ?

Trauma
  • 25,297
  • 4
  • 37
  • 60

2 Answers2

11

Voy a quitar el azúcar sintáctico de tu código para que se vea claro qué se está haciendo:

//se queda como está
function sleep( ms ) {
  return new Promise( resolve => setTimeout( resolve, ms ) );
}

function demoNoSugar() {
  console.log( 'Taking a break...' );
  let promesa = sleep(2000);
  return promesa.then(() => console.log( 'Two seconds later' ));
}


function demo2NoSugar() {
  console.log( 'demo2 -> before' );
  let promesa = demoNoSugar();
  return promesa.then(()=> console.log( 'demo2 -> after' ));
}

function demo3NoSugar( ) {
  console.log( 'demo3 -> before' );
  demo2NoSugar( )
  console.log( 'demo3 -> after' );
}

demo3NoSugar( );

Pasos:

  1. Ejecuta demo3NoSugar, que pone en consola 'demo3 -> before'.
  2. Ejecuta demo2NoSugar, que pone en consola 'demo2 -> before'.
  3. Ejecuta demoNoSugar, que pone en cosola 'Taking a break...'.
  4. Ejecuta sleep(2000), que devuelve una promesa que se resolverá en 2 segundos. Antes de devolverla se le adjunta en demoNoSugar la orden de escribir 'Two seconds later' al resolverse.
  5. A la resolución de esa promesa se le añade otra orden de escribir 'demo2 -> after' en demo2NoSugar y se devuelve de nuevo.
  6. Estamos de nuevo en demo3NoSugar, que no captura la promesa, y simplemente termina escribiendo en consola 'demo3 -> after'.
  7. La promesa se resuelve y se escriben los dos mensajes adjuntos.

Cosas a tener en cuenta:

Una función que tenga un async, devuelve siempre una Promise. Igualmente, una función que devuelva una Promise, se puede considerar que es async (y por tanto se puede usar con await):

async function devuelve1() {
  return 1;
}

function devuelve2() {
  return new Promise(res => res(2));
}

let p1 = devuelve1();
p1.then(r => console.log(r));


async function aux() {
  let r = await devuelve2();
  console.log(r);
}

aux();
Pablo Lozano
  • 45,934
  • 7
  • 48
  • 87
7

Es porque no estás usando await con demo2()

function sleep( ms ) {
  return new Promise( resolve => setTimeout( resolve, ms ) );
}

async function demo( ) {
  console.log( 'Taking a break...' );
  await sleep( 2000 );
  console.log( 'Two seconds later' );
}

async function demo2( ) {
  console.log( 'demo2 -> before' );
  await demo( );
  console.log( 'demo2 -> after' );
}

async function demo3( ) {
  console.log( 'demo3 -> before' );
  await demo2( );
  console.log( 'demo3 -> after' );
}

demo3( );

Como podrás en la consola tienes:

demo3 -> before
demo2 -> before
Taking a break...
Two seconds later
demo2 -> after
demo3 -> after

El await, solamente no debes usarlo, para la función que ejecutas desde el main (en este caso el llamado a demo3)

Pablo Lozano
  • 45,934
  • 7
  • 48
  • 87
iMangas
  • 546
  • 2
  • 6
  • 2
    De hecho no *puedes usarlo* fuera de una función async – Pablo Lozano Mar 06 '19 at 14:30
  • 1
    Entonces, si uso una librería externa, ¿ tengo que saber si todas sus funciones son o no `async` antes de poder llamarlas ? ¿ Y usar `await` cada vez que las llame ? – Trauma Mar 06 '19 at 14:41
  • En caso de que sean async, si. Pero es lo mismo que te pasa con las promises, tienes que saber cuales lo son y usar el then()/catch() – iMangas Mar 06 '19 at 14:46