Introducción a las promesas de Javascript (ECMAScript 2015)

Introducción a las promesas de Javascript (ECMAScript 2015)

Una promesa en JavaScript es un método que eventualmente produce y retorna un valor. Esta puede ser considerada como una contraparte asíncrona de una function getter (get). La estructura de una promesa (en su uso) es bastante sencilla:

promise.then(function(valor){
    // Hacer algo con el valor
});

Javascript es asíncrono solamente en el sentído, de que puede hacer por ejemplo llamadas AJAX o los intervalos y tiempos (setTimeout). Gracias a la API de las promesas, los desarrolladores pueden evitar el uso excesivo de callbacks (aunque debes tener en cuenta que el hecho de que una función use un callback no significa que esta sea asíncrona) para manipular interacciones asíncronas que pueden ser manejadas como cualquier otra variable de manera fácil, ordenada y legible.

Las promesas pueden reemplazar por completo el uso de callbacks asíncronos y proporcionan un monton de beneficios en comparación a callbacks planos. Las promesas empiezan "una revolución" pues cada vez más y más librerias y frameworks las usan como medio primario para manipular la asincronía en su código. Desde el 2013, las promesas están disponibles de manera nativa en los exploradores modernos

Porque debería usar promesas

Las promesas de JavaScript te ayudarán a convertir todo tu complejo código asíncrono más fácil de manipular, entender y mantener.

Las promesas no tratan sobre añadir callbacks, es simplemente una utilidad. Las promesas tratan sobre algo mucho más profundo y complejo, es decir proveen una manipulación directa entre las funciones síncronas y asíncronas. Ellas representan el siguiente gran paradigma de la programación en JavaScript, pero entender el porque son tan asombrosas no es tan simple así que sigue leyendo.

Creando tu primer promesa

Para entender el concepto, te expondre la manera más facil de usar una promesa en Javascript. El constructor Promise toma un solo argumento, un callback (una función) que espera 2 parámetros, resolve y reject. Ahora dentro de la función primaria tendrá todo el código asíncrono que encapsulará esta promesa, finalmente si todo funciona correctamente ejecuta la funcion resolve, de otra manera rechaza enviando un error usando la funcion reject. Crea tu primera promesa con el siguiente código:

var miPrimeraPromesa = new Promise(function(resolve,reject){

    // Haz una tarea asíncrona aquí (XMLHttpRequest por ejemplo)
    // Lo que quieras
    // Finalmente resuelvela o rechazala

    var aceptada = true;

    // Agrega una condición para saber si enviar el callback de error o no
    if(aceptada){
        resolve("Enviar este valor");
    }else{
        reject("Enviar este error");
    }
});

// Ejecuta tu promesa !
miPrimeraPromesa.then(function(successData){
    // Imprime : "Enviar este valor"
    console.info(successData);
}).catch(function(errData){
    // Imprime : "Enviar este error"
    console.error(errData);
});

Todas las instancias de una promesa tendrán un metodo que te permite ejecutar algo cuando la promesa es cumplida. El método then recibe un callback que recibe a su vez la información enviada por la funcion resolve de la promesa.

Puedes adjuntar más de un callback a la promesa si deseas. Cada función then que agregues será ejecutada consecutivamente en el orden en el que fueron asignadas. La unica diferencia, es que despues de la primera función then que agregues, el valor recibido como primer parámetro, será el valor retornado de la funcion then anterior. Por ejemplo :

var unaPromesa = new Promise(function(resolve,reject){
    resolve("Enviar este valor");
});

unaPromesa.then(function(successData){
    // Imprime : "Enviar este valor"
    console.info(successData);
    return "Pero ahora retornare esto";
}).then(function(data){
    // Imprime "Pero ahora retornare esto"
    console.log(data);
    return 1000;
}).then(function(data){
    // Imprime 1000
    console.log(data);
});

Facil de entender, verdad ? ahora debes aprender a manejar los errores retornados por la promesa.

La función catch del callback es ejecutado cuando una promesa es rechazada usando la funcion reject dentro de la promesa. Puedes incluso en vez de usar la función catch, proporcionar una funcion como segundo parámetro de la función then que será ejecutada en caso del uso de la función reject dentro de la promesa.

PrimeraPromesa.then(function(data) {
    console.log(data);
}, function(err) {
    console.error(err);
});

// O usa catch
PrimeraPromesa.then(function(data) {
    console.log(data);
}).catch(function(err) {
    console.error(err);
})

Nota: solamente 1 callback de error será ejecutado o el segundo parámetro de la función then o la función catch , nunca ambos.

Promise.resolve & Promise.reject

A veces, por diferentes razones no necesitarás repetír una función asíncrona muchas veces, por ejemplo una llamada ajax que busca algo de información y está puede ser guardada en un tipo de "caché" propio o alguna manera de guardar información localmente.

En este caso, te expondre un ejemplo que usa la función fetch que implementa la interfaz de una promesa. La función fetch hará la solicitud al servidor cada vez que la función es ejecutada, sin embargo sería más viable que si esa información esta disponible localmente, retornar ese objeto en vez de hacer el llamado al servidor una vez más. Usa Promise.resolve y Promise.reject simplemente para retornar el resultado de una promesa sin usar la sintáxis con constructor completa.

var cacheUserAge = {};

function getEdadUsuario(id){

    // Si el id de usuario está en el objeto
    // No lo obtenga del servidor
    // retorne el objeto almacenado localmente en su lugar
    if(cacheUserAge[id]){
        return Promise.resolve(cacheUserAge[id].edad);
    }

    // Si quieres retornar un error, simplemente usa Promise.reject en vez de Promise.resolve
    // para activar el callback catch

    return fetch('api/userage/' + id + '.json')
    .then(function(data) {
        // Guardar en "cache" local
        cacheUserAge[id] = data;
        // Retornar el valor para el siguiente then callback
        return data.edad;
    }).catch(function() {
        throw new Error('No se pudo obtener usuario: ' + id);
    });
}


// Then use it
var usuarioId = 15;

getEdadUsuario(usuarioId).then(function(edad){
    alert(edad);
});

Cumpliendo multiples promesas a la vez

La api de las promesas te permite resolver multiples promesas a la vez usando el método Promise.all. Este método toma como argumento un array de promesas y retorna una promesa que será cumplida si y solo si todas son cumplidas satisfactoriamente. Al ser cumplidas, obtendrás un array de resultados (cual sea el valor que retornaba cada promesa) con la misma indexación (orden) en la que estában en el array original.

var PrimeraPromesa = new Promise(function(resolve,reject){
    resolve("Primera promesa");
});

var SegundaPromesa = new Promise(function(resolve,reject){
    resolve("Segunda promesa");
});

var promesasPorSerCumplidas = [PrimeraPromesa,SegundaPromesa];

// Imprime ["Primera promesa","Segunda promesa"]
Promise.all(promesasPorSerCumplidas).then(function(resultados){
    console.info(resultados);
});

Nota: puedes agregar la función catch a Promise.all para atrapar errores, recuerda que si alguna de las promesas falla, este callback será ejecutado y se detendrá la ejecución de las demás.

Promise.race

Promise.race será el informante, de una carrera literal entre un grupo de promesas. Al igual que Promise.all, esta recibe como primer parámetro un array de promesas a ser ejecutadas, sin embargo esta función es completamente opuesta a Promise.all en el sentido de que esta retorna solamente el resultado (o el error) de la primera promesa que es cumplida, mientras que las otras serán simplemente ignoradas.

var p1 = new Promise(function(resolve, reject) { 
	setTimeout(function() { resolve('p1!'); }, 5000);
});

var p2 = new Promise(function(resolve, reject) {
	setTimeout(function() { resolve('p2!'); }, 10000);
});

// A correr !
Promise.race([p1, p2]).then(function(winner) {
    // Imprime p1!
	console.log('El ganador es : ', winner);
}).catch(function(one, two) {
	console.log('Error: ', one);
});

Soporte multiexplorador

La API de promesas está disponible desde Chrome 32, Opera 19, Firefox 29, Safari 8 & Microsoft Edge.

Polyfill

Como se menciono anteriormente, la introducción de la API no cubre versiones antiguas de exploradores, pero hay bastantes implementaciones de polyfill para ser usadas y agregar soporte a las promesas en estos exploradores. Te recomendamos usar explicitamente el polyfill es6-promise polyfill que puede ser obtenido en el repositorio oficial de stefanpenner en Github aquí.

El polyfill cubre todos los escenarios posibles de la API, sin embargo hay algunas limitaciones en cuanto a Internet Explorer se refiere (<= 9) pero que puede ser resuelta usando una sintaxis diferente pues catch es una palabra clave reservada, esto significa que promesa.catch(func) provocaria un error de sintaxis. Para evadir esto, puedes usar el principio key-value al ser un objeto para poder usarlo como se muestra en el siguiente ejemplo:

promesa['catch'](function(err) {
    // ...
});

// O simplemente usa el segundo parametro de la funcion then
promesa.then(undefined, function(err) {
  // ...
});

Nota: dicha tecnica es implementada automaticamente por los minificadores de Javascript mas comúnes, que hace tu código seguro y usable por exploradores viejos en producción. Sin embargo, para prevenir cualquier incompatibilidad, usa el segundo parámetro de la función then en vez de usar catch.

Hay un monton de implementaciones (algunas de "intento") de promesas disponibles para los desarrolladores. Por ejemplo, jQuery's Deferred, Microsoft's WinJS.Promise, when.js,q, y dojo.Deferred. Sin embargo se cuidadoso de cual usas pues no todas de ellas sigue los estándares de las promesas de Javascript.

Ahora que sabes como usar las promesas, comienza a implementarlas en tus proyectos. Que te diviertas !

Esto podría ser de tu interes

Conviertete en un programador más sociable