Explicando Promises de JavaScript con un ejemplo simple

Hace unos días tuve la oportunidad de ver, una vez más, explicado el concepto de promesas y de objetos asíncronos, cuyo objetivo es, entre otros, evitar el llamado «callback-hell» que surge cuando llamamos a una función asíncrona en JavaScript.

En nuestro código frontend, es bastante habitual encontrarnos con funciones con este aspecto:

function myFunction(arguments,onSuccess,onError){
//Do things...
}

myFunction({val: 12}, function(){
    //Do things on success
}, function(){
    //Do things on error
});

El problema surge cuando encadenamos varias llamadas asíncronas, y ya no somos capaces de saber en qué función estamos ní cómo hemos llegado allí, además que el código se hace más difícil de leer.

Las promises o promesas, son objetos que nos permiten mejorar la legibilidad de nuestro código y evitar tener que pasar el contenido de las funciones directamente como argumentos a nuestra llamada.

En este artículo veremos una implementación muy simple de una promesa, para ver cómo funciona realmente, y comprobar que la base es un objeto simple con ciertas propiedades.

Nuestra clase Promise

Veamos por encima qué tenemos en nuestra clase Promise:

function Promise(){

    var self = this;
    var thenCallback = null;

    self.then = function(callback){
        thenCallback = callback;
    };

    self.complete = function(args){
        if (thenCallback && typeof thenCallback === 'function'){
            thenCallback(args);
        }
    };
}

Esta clase define dos funciones, una función then, en la que especificamos la función a ejecutar al terminar la acción, y una función complete, que nos permite ejecutar la función que hayamos definido, pasandole los argumentos necesarios.

Veamos un ejemplo de uso:

function myFunction(){

    var p = new Promise();
    setTimeout(p.complete, 1000)
    return p;
}

var promise = myFunction()
promise.then(function(){
    console.log('done');
});

En este caso, por una parte simulamos una llamada asíncrona con la función setTimeout, y por otra parte devolvemos la promesa. En el código que llama a la función, nos suscribimos al resultado de esa promesa, y podemos separar la ejecución de la función del tratamiento del resultado de la misma.

Gestión de errores y encadenamiento de promesas

Además de ejecutar funciones cuando todo ha ido bien, hemos de ser capaces de ejecutar acciones cuando se ha producido un error.

Para ello solamente tenemos que extender nuestra clase, agregando dos funciones adicionales, una se suscriba al error, que llamaremos error, y otra que ejecute la acción de error, que llamaremos fail.

self.error = function(callback){
        failCallback = callback;
};

self.fail = function(args){
    if (failCallback && typeof failCallback === 'function'){
        failCallback(args);
    }
}

Por otra parte, mediante un pequeño cambio podemos encadenar resultados:

self.error = function(callback){
        failCallback = callback;
        return self;
};

Este encadenamiento nos permite tener interfaces más fluidas, de tal manera que por una parte nos suscribimos al resultado de una función, y por otra parte al posible error:

promise.then(function(args){
    console.log('foo has happened ' + args);
}).error(function(args){
    console.log('error has happened');
});

Funciones «always»

Además de código que se ejecute cuando todo ha ido bien, o cuando todo ha ido mal, es posible que necesitemos código que se ejecute siempre, independientemente del resultado, lo que sería equivalente a la directiva «finally» de una función de C#.

Para ello solamente necesitamos agregar un nuevo callback a nuestra lista, que se ejecutará en caso de éxito o de fallo.

Conclusiones

Como vemos, las promesas no son «magia» que tienen algunas bibliotecas de JavaScript. Simplemente es otra sintaxis, otra manera de recuperar el control tras volver de una función asíncrona.

Si echamos un vistazo a lo que se propone para ECMAScript 6, lo que en la práctica será la próxima versión de JavaScript, veremos que las ideas son muy similares a lo que hemos visto en este artículo, aunque más extendido y detallado.

Tienes el código completo de la promesa en este gist

Enlaces adicionales

Diferentes tipos de «Promises»

Gracias a Antón Molleda (@molant) por los enlaces!

Artículo Invitado: ECMAScript 6 y la nueva era de JavaScript por @CKGrafico

Comenzamos con este artículo una nueva sección llamada «Artículos Invitados», en este caso tengo la suerte de contar con Quique Fernandez Guerra, Microsoft Student Partner y desarrollador Javascript:

Quique Fdez GuerraQuique Fdez. Guerra
Desarrollador y amante de JavaScript
Twitter: @CKGrafico
Web: CKGrafico.com

Quique es un usuario muy activo de comunidades de desarrollo frontend, y le hemos visto en varios hangouts mostrando su experiencia, así que le he pedido que nos cuente sus impresiones de lo que está por venir en la nueva versión del estandar ECMAScript 6, en el cual se basarán las próximas versiones de Javascript. A continuación sus impresiones


Desde siempre se ha considerado a JavaScript cómo un lenguaje simplón, es seguramente uno de los lenguajes actuales más sencillos de aprender, pero además es conocido por no ser muy completo (ya sea por la manera de trabajar con las clases, o los artículos sobre ‘JavaScript the weird parts’).

Por otro lado, en los últimos años JavaScript a empezado a tener más importancia hasta el punto que se ha vuelto el único lenguaje universal, es decir, lo encontramos en web, servidor, hardware, móvil, etc, cómo ya comenté en un artículo hace un tiempo.

Aún así el lenguaje sigue igual, de hecho ha llegado un momento en que la gente sabe mucho más sobre frameworks del lenguaje que del propio lenguaje. Está lleno de ofertas de trabajo en las que se pide conocimientos en jQuery y Backbone o Angular y por supuesto hay mucha gente aprendiendo directamente estos frameworks (Aprender un framework antes de saber el propio lenguaje…).

Por suerte, parece que con la nueva versión del estándar ECMAScript vamos a poder tomar más en serio JavaScript, aunque cómo siempre vamos a tener que esperar que todos los navegadores lo soporten. A continuación voy a explicar las partes que considero más importantes de esta nueva versión.

Declaración let

Ahora podemos declarar variables que existan solo en un bloque de código, entendiendo por bloque de código desde una función hasta un bucle o una condición.


if(1) {
var a = 5;
let b = 6;
console.log(a,b); // 5 6
}

console.log(a,b); // b is not defined

Constantes, ¡¡Sí constantes!!

Ya era hora de poder utilizar constantes en nuestro apreciado JavaScript, muy sencillas de utilizar igual que en otros lenguajes.

const PI = 3.14;
console.log(PI); // 3.14

function test() {
 console.log('test', PI); // test 3.14
}

test();

PI = 6; // Error!!

Arrow functions

Si trabajas con lenguajes cómo C# que tienen lambda verás que es algo parecido, que se ha traído a JavaScript.

let simple = a => a / 2;
console.log(simple(8)); // 4

let complex = (a, b) => {
 if (a < b) {
 return a;
 }
 return b;
}

console.log(complex(3,6)); // 3

Valores por defecto en parámetros

Otro ejemplo más de algo que tienen casi todos los lenguajes y JavaScript aún no lo tenía, pero ya está aquí.

function test(a, b = 10) {
 return a + b;
}

console.log(test(1)); // 11
console.log(test(1,2)); // 3

Rest parameters

Nos va a ser muy útil cuando no sepamos el número de parámetros exactos, muchas veces usábamos arguments para eso, pero ahora vamos a tenerlo mucho más fácil.

function test(a, b = 1, ...c) {
 return a + b + c.length || 0;
}

console.log(test(1)); // 2
console.log(test(1,10,1,1,1,1)); // 15

Pero no solo eso, también lo vamos a poder utilizar en un array o al llamar a una función.

let arr = [4, 5, 6];
let numbers = [1, 2, 3, ...arr, 7, 8];

console.log(numbers); // [1, 2, 3, 4, 5, 6, 7, 8]

Object literals

Simplificaremos los objetos, de manera que no haya que poner información irrelevante.

// ECMA 5
var world = 'world';
var obj = {
 world: world,
 hello: function() {
 console.log('hello', this.world);
 }
};

obj.hello(); // hello world

// ECMA 6
let world6 = 'world';
let obj6 = {
 world6,
 hello() {
 console.log('hello6', this.world6);
 }
};

obj6.hello(); // hello6 world

for..of

La evolución lógica del for..in

// ECMA 5
var arr = ['a', 'b', 'c'];
for(var i in arr) {
 console.log(arr[i]); // a, b, c
}

// ECMA 6
let arr6 = ['a', 'b', 'c'];
for(let i of arr6) {
 console.log(i); // a, b, c
}

Generadores

Un nuevo tipo de función que nos ayuda a crear un iterador de manera fácil, utilizan yield en vez de return que es una nueva palabra reservada.

function* range(start, end) {
 for (let i = start; i <= end; i++) {
 yield i;
 }
}

var ranger = range(1,10);

var res = {
 value: null,
 done: false
};

while(!res.done){
 res = ranger.next();
 console.log(res.value, res.done);
}

Symbol

symbol es un nuevo tipo de dato y Symbol() nos devuelve un objeto único de tipo symbol, pensado para tener objetos o métodos que son distintos de los demás, sería un sustituto a usar cadenas para los nombres de los métodos por ejemplo.

let myMethod = Symbol();
let obj = {
 [myMethod]() {
 console.log('Soy único e irrepetible');
 }
};

obj[myMethod](); // Soy único e irrepetible
console.log(typeof myMethod); // symbol

Map

El objeto Map  es un objeto de clave / valor, la mejor manera de entenderlo es ver un ejemplo.

let map = new Map();
let arr = [];
map.set('string', 'Ola k ase');
map.set(arr, [1, 2, 3, 4]);
map.set(typeof 8, 'I\'m a number');

console.log(map.size); // 4

console.log(map.get('string')); // Ola k ase
console.log(map.get(typeof 1)); // I'm a number
console.log(map.get([])); // undefined -> arr !== []
console.log(map.get(arr)); // [1, 2, 3, 4]

Set

Sirve para poder crear algo parecido a las listas en otros lenguajes, aunque aún bastante simple.

let set = new Set();
set.add('test');
set.add(8);
set.add('Quique');

console.log(set.size); // 3
console.log(set.has(8)); // true

set.delete('Quique');

set.forEach(function(value) {
 console.log(value); // test 8
});

Clases

Vamos a tener clases en condiciones, más o menos van a hacer lo mismo que hasta ahora pero vamos a tener una sintaxis más sencilla para implementarlas.

class Human {
 constructor(name = 'anonymous') {
 this.name = name;
 }

 hello() {
 console.log('Welcome to the world ' + this.name);
 }
}

class Male extends Human {
 constructor(name) {
 super(name);
 this.sex = 'male';
 }

 get age() {
 return this._age;
 }

 set age(age) {
 this._age = age;
 }
}

let me = new Male('Quique');
me.hello();
me.age = 1;
console.log(me.age); // 1

Módulos

Ha llegado la hora de poder crear partes de nuestro código por separado e importarlas cuando nos sea conveniente, para no cargarlo todo de golpe y trabajar de una manera ordenada.

// number.js
module 'number' {
 let num = 8;
 export class Number {
     constructor() {
         console.log('The number is: ' + num);
     }
 }

}

// app.js
module number from '/number.js';
import Number from 'number';

var n = new Number(); // The number is 8

Templates

Una de las cosas que más he usado este último año en JavaScript han sido los templates, sobretodo trabajando Handlebars, pues ahora mismo se convierten en algo propio de este lenguaje.

let name = 'Quique', twitter = 'CKGrafico';
let template = `Hello, this is ${name}, find my on ${twitter}`;
// Nota, fíjate en las ` que no es lo mismo que '
console.log(template); // Hello, this is Quique, find my on CKGrafico

Estas son las cosas más destacables que trae la nueva versión, puedes encontrar la lista entera en su especificación.

Mientras esperas a que sea compatible en los navegadores puedes trabajar con ECMA6 en ES6 fiddle (un sandbox) o utilizar un pequeño transpiler llamado Traceur que te permite trabajar en ECMA6 y compilar a ECMA5

 


Si tienes preguntas o comentarios no dudes en dejarlas en la sección de comentarios, y si tienes más información sobre Quique, aquí tienes algunos datos de contacto:

Quique Fdez GuerraQuique Fdez. Guerra
Desarrollador y amante de JavaScript
Twitter: @CKGrafico
Web: CKGrafico.com