JavaScript asincrónico: devoluciones de llamada y promesas explicadas

Tratar con código asincrónico, es decir, código que no se ejecuta inmediatamente como solicitudes web o temporizadores, puede ser complicado. JavaScript nos ofrece dos formas de manejar el comportamiento asincrónico: devoluciones de llamada y promesas.

Las devoluciones de llamada fueron la única forma compatible de forma nativa para tratar con código asíncrono hasta 2016, cuando Promisese introdujo el objeto en el lenguaje. Sin embargo, los desarrolladores de JavaScript habían estado implementando una funcionalidad similar por sus propios años antes de que aparecieran las promesas. Echemos un vistazo a algunas de las diferencias entre devoluciones de llamada y promesas, y veamos cómo manejamos la coordinación de múltiples promesas.

Las funciones asincrónicas que utilizan devoluciones de llamada toman una función como parámetro, que se llamará una vez que se complete el trabajo. Si ha utilizado algo como setTimeouten el navegador, ha utilizado devoluciones de llamada.

// Puede definir su devolución de llamada por separado ...

deja myCallback = () => {

  console.log ('¡Llamado!');

};

setTimeout (myCallback, 3000);

//… pero también es común ver devoluciones de llamada definidas en línea

setTimeout (() => {

  console.log ('¡Llamado!');

}, 3000);

Por lo general, la función que recibe una devolución de llamada la toma como su último argumento. Este no es el caso anterior, así que supongamos que hay una nueva función llamada waitque es como setTimeoutpero toma los dos primeros argumentos en orden opuesto:

// Usaríamos nuestra nueva función así:

waitCallback (3000, () => {

  console.log ('¡Llamado!');

});

Callbacks anidados y la pirámide de la fatalidad

Las devoluciones de llamada funcionan bien para manejar código asincrónico, pero se vuelven complicadas cuando comienza a tener que coordinar múltiples funciones asincrónicas. Por ejemplo, si quisiéramos esperar dos segundos y registrar algo, luego esperar tres segundos y registrar algo más, luego esperar cuatro segundos y registrar algo más, nuestra sintaxis se anida profundamente.

// Usaríamos nuestra nueva función así:

waitCallback (2000, () => {

  console.log ('¡Primera devolución de llamada!');

  waitCallback (3000, () => {

    console.log ('¡Segunda devolución de llamada!');

    waitCallback (4000, () => {

      console.log ('¡Tercera devolución de llamada!');

    });

  });

});

Esto puede parecer un ejemplo trivial (y lo es), pero no es raro realizar varias solicitudes web seguidas en función de los resultados devueltos de una solicitud anterior. Si su biblioteca AJAX usa devoluciones de llamada, verá cómo se desarrolla la estructura anterior. En los ejemplos que están anidados más profundamente, verá lo que se conoce como la pirámide de la fatalidad, que recibe su nombre de la forma de la pirámide hecha en el espacio en blanco con sangría al comienzo de las líneas.

Como puede ver, nuestro código se deforma estructuralmente y es más difícil de leer cuando se trata de funciones asincrónicas que deben suceder secuencialmente. Pero se vuelve aún más complicado. Imagínese si quisiéramos iniciar tres o cuatro solicitudes web y realizar alguna tarea solo después de que todas hayan regresado. Te animo a que intentes hacerlo si no te has encontrado con el desafío antes.

Async más fácil con promesas

Las promesas proporcionan una API más flexible para manejar tareas asincrónicas. Requiere que la función se escriba de manera que devuelva un Promiseobjeto, que tiene algunas características estándar para manejar el comportamiento posterior y coordinar múltiples promesas. Si nuestra waitCallbackfunción estuviera Promisebasada en-, solo tomaría un argumento, que son los milisegundos para esperar. Cualquier funcionalidad posterior quedaría encadenada a la promesa. Nuestro primer ejemplo se vería así:

deje myHandler = () => {

  console.log ('¡Llamado!');

};

waitPromise (3000) .then (myHandler);

En el ejemplo anterior, waitPromise(3000)devuelve un Promiseobjeto que tiene algunos métodos para que los usemos, como then. Si quisiéramos ejecutar varias funciones asincrónicas una tras otra, podríamos evitar la pirámide de la fatalidad usando promesas. Ese código, reescrito para respaldar nuestra nueva promesa, se vería así:

// No importa cuántas tareas asíncronas secuenciales tengamos, nunca hacemos la pirámide.

WaitPromise (2000)

  .entonces (() => {

    console.log ('¡Primera devolución de llamada!');

    return waitPromise (3000);

  })

  .entonces (() => {

    console.log ('¡Segunda devolución de llamada!');

    return waitPromise (4000);

  })

  .entonces (() => {

    console.log ('¡Segunda devolución de llamada!');

    return waitPromise (4000);

  });

Mejor aún, si necesitamos coordinar tareas asincrónicas que admitan Promesas, podríamos usar all, que es un método estático en el Promiseobjeto que toma varias promesas y las combina en una. Eso se vería así:

Promise.all ([

  WaitPromise (2000),

  waitPromise (3000),

  esperar Promesa (4000)

]). then (() => console.log ('¡Todo está hecho!'));

La próxima semana, profundizaremos en cómo funcionan las promesas y cómo usarlas idiomáticamente. Si recién está aprendiendo JavaScript o está interesado en probar sus conocimientos, intente waitCallbacko intente lograr el equivalente a Promise.allcon devoluciones de llamada.

Como siempre, comuníquese conmigo en Twitter con cualquier comentario o pregunta.