Introducción a las Promesas de ES6

  • Por
En este artículo te vamos a explicar qué son las promesas, como usarlas y cómo crear funciones que implementan promesas.

Las promesas son herramientas de los lenguajes de programación que nos sirven para gestionar situaciones futuras en el flujo de ejecución de un programa. Aunque es un concepto que usamos en Javascript desde hace un relativamente corto espacio de tiempo, ya se viene implementando en el mundo de la programación desde la década de los 70.

Las promesas se originaron en el ámbito de la programación funcional, aunque diversos paradigmas las han incorporado, generalmente para gestionar la programación asíncrona. En resumen, nos permiten definir cómo se tratará un dato que sólo estará disponible en un futuro, especificando qué se realizará con ese dato más adelante.

Aunque en Javascript se introducen en el estándar en ES6, lo cierto es que se vienen usando desde hace tiempo, ya que varias librerías las habían implementado para solucionar sus necesidades de una manera más elegante, e incluso existen bibliotecas independientes en Javascript que tenían como único propósito facilitar la creación de promesas, antes que el propio Javascript "Vanilla" las incorporarse.

Ahora con ES6 podemos beneficiarnos de sus ventajas a la hora de escribir un código más limpio y claro. De hecho son una de las novedades más destacadas de ES6.

Cómo usar promesas

Creo que para comenzar a entender las promesas nos viene muy bien comenzar viendo cómo podemos gestionarlas, es decir, qué se debe hacer cuando ejecutamos funciones que nos devuelven promesas. Luego aprenderemos a crear funciones que implementan promesas y veremos que es también bastante fácil.

Por ejemplo, la función set() de Firebase, para guardar datos en la base de datos en tiempo real, devuelve una promesa cuando se realiza una operación de escritura.

Nota: Da igual que no conozcas Firebase, y aunque puedes aprender en el Manual de Firebase, no es necesario para entender las promesas. Simplemente quiero que se entienda cómo gestionar una promesa. Olvida que esta función pertenece al API de Firebase, concéntrate en el esquema de trabajo de promesas, que es siempre el mismo.

Tiene sentido que se use una promesa porque, aunque Firebase es realmente rápido, siempre va a existir un espacio de tiempo entre que solicitamos realizar una escritura de un dato y que ese dato se escribe realmente en la base de datos. Además, la escritura podría dar algún tipo de problema y por tanto producirse un error de ejecución, que también deberíamos gestionar. Todas esas situaciones se pueden implementar por medio de dos métodos:

  • then: usado para indicar qué hacer en caso que la promesa se haya ejecutado con éxito.
  • catch: usado para indicar qué hacer en caso que durante la ejecución de la operación se ha producido un error.

Ambos métodos debemos usarlos pasándoles la función callback a ejecutar en cada una de esas posibilidades.

referenciaFirebase.set(data)
  .then(function(){
    console.log('el dato se ha escrito correctamente');
  })
  .catch(function(err) {
    console.log('hemos detectado un error', err');
  });

Fíjate que "referenciaFirebase.set(data)" nos devuelve una promesa. Sobre esa promesa encadenamos dos métodos, then() y catch(). Esos dos métodos son para gestionar el futuro estado de la escritura en Firebase y están encadenados a la promesa. Por si acaso no se entiende eso, podríamos leer este mismo código de esta manera.

referenciaFirebase.set(data).then(function(){
  console.log('el dato se ha escrito correctamente');
}).catch(function(err) {
  console.log('hemos detectado un error', err');
});

Igual así se ve mejor qué es lo que me refiero cuando digo que están encadenados. Insisto en esto para que nos demos cuenta que los then() y catch() forman parte de la misma cosa (la promesa devuela por el métood set() de Firebase) y porque encadenar promesas es algo bastante normal y así nos vamos familiarizando mejor con cosas que usaremos en un futuro próximo.

Nota: Estoy escribiendo código más parecido a ES5, porque así nos quedamos solo con la parte nueva, las promesas y no nos despistamos con sintaxis que quizás todavía no tienes perfectamente asimilada como las arrow functions. Pero obviamente, esas funciones callback enviadas a then() y catch() podrían ser perfectamente expresadas con arrow functions de ES6.

Otro detalle que no debe pasar desapercibido es que la promesa puede devolver datos. Es muy normal que esto ocurra. Por ejemplo queremos recibir algo de una base de datos y cuando la promesa se ejecuta correctamente querremos que nos llege ese dato buscado. No es el caso en este método set() de Firebase, porque una operación de escritura no te devuelve nada en esta base de datos, pero insisto que es algo bastante común.

En el caso que la promesa te devuelva un dato, lo podrás recibir como parámetro en la función callback que estás adjuntando al then().

funcionQueDevuelvePromesa()
  .then( function(datoProcesado){
    //hacer algo con el datoProcesado
  })

Nota: Como estás viendo, al usar una promesa no estoy obligado a escribir la parte del catch, para procesar un posible error, ni tan siquiera la parte del then, para el caso positivo.

En el caso negativo implementado mediante el catch() siempre vamos a recibir un dato, que es el error que se ha producido al ejecutar la promesa y el causante de estar procesándose el correspondiente catch. Volviendo al ejemplo de antes, método set(), observa que el error lo hemos recibido en el parámetro de la función callback indicada en el catch().

Pirámide de callbacks

Seguro que habrás oído hablar del código spaguetti. Uno de los síntomas en Javascript de ello es lo que se conoce como pirámide de callbacks o "callback hell". Hay mucha literatura y ejemplos en Javascript sobre ello. Ocurre cuando quieres hacer una operación asíncrona, a la que le colocas un callback para continuar tu ejecución. Luego quieres encadenar una nueva operación cuando acaba la anterior y otra nueva cuando acaba ésta.

El método setTimeout() de toda la vida en Javascript nos sirve para escribir algo de código spaguetti y ver la temida pirámide de callbacks.

setTimeout(function() {
  console.log('hago algo');
  setTimeout(function() {
    console.log('hago algo 2');
    setTimeout(function() {
      console.log('hago algo 3');
      setTimeout(function() {
        console.log('hago algo 4');
      }, 1000)
    }, 1000)
  }, 1000)
}, 1000);

En resumen lo que hacemos es encadenar una serie de tareas, para realizarlas secuencialmente, una cuando acaba la otra. Funciona, pero ese código es un infierno para mantener, pues tiene difícil lectura y cuesta meterle mano para implementar nuevas funcionalidades. Las promesas nos pueden ayudar a mejorarlo, pero primero vamos a tener que aprender a implementarlas nosotros mismos.

Implementar una promesa

Ahora viene la parte interesante, en la que aprendemos a crear nuestras propias funciones que devuelven promesas. Esto se consigue mediante la creación de un nuevo objeto "Promise", como veremos a continuación. Pero antes de ponernos con ello debes tener bien claro el objetivo de una promesa: "hacer algo que dura un tiempo y luego tener la capacidad de informar sobre posibles casos de éxito y de fracaso"

Ahora verás el código y aunque pueda parecer confuso al principio, la experiencia usando promesas te lo irá clarificando naturalmente. Ten en cuenta que para crear un objeto "Promise" voy a tener que entregarle una función, la encargada de realizar ese procesamiento que va a tardar algo de tiempo. En esa función debo ser capaz de procesar casos de éxito y fracaso y para ello recibo como parámetros dos funciones:

  • La función "resolve": la ejecutamos cuando queremos finalizar la promesa con éxito.
  • La función "reject": la ejecutamos cuando queremos finalizar una promesa informando de un caso de fracaso.

function hacerAlgoPromesa() {
  return new Promise( function(resolve, reject){
    console.log('hacer algo que ocupa un tiempo...');
    setTimeout(resolve, 1000);
  })
}

Como puedes ver, nuestra función hacerAlgoPromesa() devolverá siempre una promesa (return new Promise). Se encarga de hacer alguna cosa, y luego ejecutará el método resolve (1 segundo después, gracias al serTimeout).

Nota: Aun no estamos controlando posibles casos de fracaso, pero de momento está bien para no liarnos demasiado.

Esa misma función algunos programadores la preferirían ver escrita de este otro modo.

function hacerAlgoPromesa(tarea) {
  function haciendoalgo(resolve, reject) {
    console.log('hacer algo que ocupa un tiempo...');
    setTimeout(resolve, 1000);
  }
  return new Promise( haciendoalgo );
}

Es exactamente lo mismo que teníamos antes, solo que se ha ordenado el código de otra manera. Usa la alternativa que veas más clara.

Ahora vamos a ver cómo ejecutar esta función que nos devuelve una promesa, aunque si entendiste el principio del artículo ya lo tendrás bastante claro.

hacerAlgoPromesa()
  .then( function() {
    console.log('la promesa terminó.');
  })

Encadenar promesas

Como colofón a esta introducción a las promesas de ES6 queremos ver cómo nos facilitan la vida, creando un código mucho más limpio y entendible que la famosa pirámide de callbacks que hemos visto en un punto anterior. Si no estás familiarizado con el tema estoy seguro que te sorprenderás. Para que sea así, vamos directamente con el código fuente:

Imagina que quieres hacer algo y repetirlo por cuatro veces, ejecutando la función hacerAlgoPromesa() repetidas veces, de manera secuencial, una después de la otra.

hacerAlgoPromesa()
.then( hacerAlgoPromesa )
.then( hacerAlgoPromesa )
.then( hacerAlgoPromesa )

Eso, comparado con el spaguetti code de antes, tiene su diferencia ¿no? y es básicamente lo mismo, ejecutar una acción 4 veces con un retardo entre ellas.

Nota: Obviamente en la realidad generalmente no repites lo mismo cuatro veces la misma operación, aunque podría ser, sino que puedes encadenar cuatro promesas distintas, una detrás de la otra, para realizar varias tareas diferentes.

Conclusión

A partir de aquí queda todavía por abordar diferentes puntos interesantes y útiles, como controlar posibles casos de error e informar de ellos en nuestras promesas, o poder ejecutar varias promesas en paralelo, en vez de secuencialmente. Todo eso lo iremos tratando, aunque espero que con este artículo se te abra un poco de luz y que puedas apreciar algunas de las ventajas de usar promesas ES6.

Ejemplo adicional de promesas en Javascript ECMAScript 2015

Si quieres investigar algo más sobre encadenar promesas, piensa que a veces a las funciones que devuelven promesas les tienes que pasar parámetros. ¿Cómo escribirías el chaining de promises? Para ser más claros, echa un vistazo a esta promesa.

function hacerAlgoPromesa2(tarea) {
  function haciendoalgo(resolve, reject) {
    console.log('Hacer ' + tarea + ' que ocupa un tiempo...');
    setTimeout(resolve, 1000);
  }
  return new Promise( haciendoalgo );
}

Es casi casi lo mismo que teníamos antes, solo que ahora le podemos pasar la tarea que quieres realizar. Lo que queremos es encadenar cuatro tareas diferentes, para ejecutar en secuencial, igual que antes. Pero tienes que pasarles parámetros distintos.

La solución la encuentras en el siguiente pedazo de código.

hacerAlgoPromesa('documentar un tema')
.then(function() {
  return hacerAlgoPromesa('escribir el artículo')
})
.then(function() {
  return hacerAlgoPromesa('publicar en desarrolloweb.com')
})
.then(function() {
  return hacerAlgoPromesa('recibir vuestro apoyo cuando compartís en vuestras redes sociales')
})

Échale un vistazo y trata de entenderlo. La clave es que para encadenar promesas la función a ejecutar como callback debe devolver también una nueva promesa.

Nos hemos quitado de en medio la pirámide de callbacks, ero tampoco creas que sería el mejor código para resolver este problema. Ya que estamos en un Manual de ES6, no queremos perder la oportunidad de mostrar un ejemplo del azúcar sintáctico que nos ofrecen las Arrow Functions.

Este código sería equivalente al anterior:

hacerAlgoPromesa('documentar un tema')
.then(() => hacerAlgoPromesa('escribir el artículo'))
.then(() => hacerAlgoPromesa('publicar en desarrolloweb.com'))
.then(() => hacerAlgoPromesa('...compartís en vuestras redes sociales'))

Mucho más limpio, no?

Autor

Miguel Angel Alvarez

Miguel es fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Comenzó en el mundo del desarrollo web en el año 1997, transformando su hobby en su trabajo.

Compartir

Comentarios

Leo

23/9/2016
Promesas
Excelente artículo Miguel!

Francisco

26/9/2016
Pedazo de artículo
De vez en cuando hacen falta artículos así que allanen el terreno a conceptos que pueden parecer intimidantes y muy avanzados pero no guardan demasiada complejidad si alguien te los explica correctamente.

¿Qué opinas de los observables y rxjs? Supuestamente se usan en Angular 2 como alternativa a las Promises por el hecho de que permiten cancelar la ejecución del código, hecho que con las promises no es posible, además de que se apuesta fuerte con Angular 2 por su uso así como en otro tipo de aplicaciones reactivas.