Promesas, promises, o cómo trabajar con código asíncrono.
Estamos acostumbrados a utilizar código que se va ejecutando una línea tras otra, pero no es la única manera que hay. Existe también lo que se denomína código asíncrono, es decir, código que se lanza para que se vaya ejecutando «en segundo plano» y cuando finalice nos avise de alguna manera.
En javascript hay 2 maneras de tratar con el código asíncrono: call backs y promises. Para trabajar con call backs se pasa como parámetro una función que se ejecutará cuando ese código asincrono termine.
Un login es un claro ejemplo de código asíncrono; tarda un tiempo y al acabar queremos que nos avise. Simulando el login con un timeout y usando una función callback que muestre un mensaje por pantalla al acabar quedaría algo así:
function hello(name) {
alert("Hola " + name);
}
function login(user, pass, callback) {
setTimeout(callback(user), 3000);
}
login("Jorge", "1234", hello);
Facil y simple, ¿no? Cuando se empiezan a anidar callbacks deja de serlo y se produce lo que se llama un callback hell:

Para evitar esto tenemos las promesas. Se definen pasándole una función que tiene 2 argumentos: resolve
y reject
, que determinan que código se ejecutará cuando la promesa finalice correcta o incorrectamente.
let promise = new Promise((resolve, reject) => {
//codigo de la promesa
});
promise.then((data) => {
//codigo que se ejecuta cuando la promesa se resuelve
});
Nota: la anotación (user) => {}
y function(user) {}
son equivalentes. Es simplemente otra manera de definir funciones anónimas añadida en es6, aunque no se les puede hacer un .bind(this)
.
El mismo login quedaría algo así:
function login(user, pass) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(user);
}, 3000);
});
}
login("Jorge", "1234").then((user) => {
console.log("Hola " + user);
});
Ya no definimos en una función qué hacer cuando se termine la ejecución, sino que devolvemos una promesa y, mediante el método then, mostramos el mensaje cuando se resuelve.
Hemos usado resolve
, con eso indicamos que la promesa se ha terminado satisfactoriamente, pero podemos usar reject
para determinar que hacer cuando sucede un error. Supongamos que si el usuario está en blanco, el login falla:
function login(user, pass) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if(user === "") {
reject("Usuario en blanco");
}
resolve(user);
}, 3000);
});
}
login("", "1234")
.then((user) => {
alert("Hola " + user);
})
.catch((error) => {
alert("Eror: " + error);
});
Al igual que con los try-catch
, existe un método finally
que se ejecuta cuando la promesa termina. Aunque no se obtiene información de la promesa, sirve para ejecutar código que sí o sí se va a tener que lanzar independientemente de que se termine correcta o incorrectamente:
login("", "1234")
.then(...)
.catch(...)
.finally(() => console.log("login finalizado"));
Si quisieramos enlazar varias promesas para que se ejecute una despues de otra, quedaría bastante más legible y claro:
function login(user, pass) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if(user === "") {
reject("Usuario en blanco");
}
resolve(user);
}, 3000);
});
}
function getFriends(user) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve([{ user: "Pepe" }, { user: "Juan" }]);
}, 2000);
});
}
function printFriends(friends) {
console.log("Amigos: " );
friends.map((friend) => console.log(friend.user));
}
login("Jorge", "1234")
.then((user) => {
console.log("Hola " + user);
getFriends(user).then((friends) => printFriends(friends));
})
.catch((error) => {
console.log("Eror: " + error);
});
Esto es una manera de ejecutar una promesa al terminarse correctamente la anterior, pero disponemos de una serie de métodos estáticos en la clase Promise
para poder ejecutar un array de promesas en paralelo.
Promise.all
Si se resuelven todas las promesas se ejecuta el then
, obteniendo un array con el resultado de todas ellas. Pero, si alguna produce un error, se ejecuta el catch
.
Promise.all([login("Jorge", "1234"), getFriends("Jorge")])
.then((results) => {
console.log("Hola " + results[0]);
printFriends(results[1]);
}
)
.catch((error) => {
console.log("Eror: " + error);
});
En este otro caso, el login se ejecuta con reject
por estar el usuario en blanco y se captura el error en el catch
:
Promise.all([login("", "1234"), getFriends("Jorge")])
.then(...)
.catch((error) => {
console.log("Eror: " + error);
});
Con el sistema anterior, esperaríamos 5 segundos en obtener el resultado (3 del login
y 2 del getFriends
), pero en este caso serían solo 3 segundos, ya que se ejecutan ambas promesas en paralelo y aunque getFriends
termine antes, hasta que login
no ha terminado no se ejecuta el then
.
Promise.allSettled
Similar a Promise.all
pero devuelve un array con el resultado de las promesas y si se resolvieron o rechazaron.
Promise.allSettled([login("", "1234"), getFriends("Jorge")])
.then((results) => { ... })
Obteniendo los siguiente datos en results:
[{
status: "rejected",
reason: "Usuario en blanco"
}, {
status: "fulfilled",
value: [{ user: "Pepe" }, { user: "Juan" }]
}]
Promise.race
Captura el resultado de la primera promesa que se resuelva o rechace, sin esperar a que todas se hayan procesado.
Promise.race([login("", "1234"), getFriends("Jorge")])
.then((result) => { ... })
.catch((error) => { ... });
¿Qué resultado obtendríamos en este caso? Como getFriends
tarda 2 segundos, se ejecutaría el then
con los amigos, aunque login
falle. En este caso, tiene poco sentido hacer esto por 2 motivos: obtendriamos los amigos de un usuario sin loguearse y además, no sabríamos que ha fallado el login
ya qué en cuanto se realiza el resolve
de getFriends
, que login
se procese con resolve
o reject
nos da igual.
Promise.any
Es similar a Promise.race
, pero captura el resultado de la primera promesa que se resuelva, independientemente de que falle otra antes. Solo lanzará el catch
si todas fallan.
Promise.race([login("", "1234"), getFriends("Jorge")])
.then((result) => { ... })
.catch((error) => { ... });
Se obtendrían los amigos, independientemente de si login
se ejecutara con reject
o resolve
.
Todo esto me parece muy bien, pero ¿cómo usamos las promesas con SAPui5? y ¿no hay otra manera más simple de usar las promesas como si fuera código sincrono del que se ejecuta línea tras línea?
Lo veremos en el próximo post, que este está quedando un poco largo.
Espero que haya quedado claro que son las promesas y os haya gustado. No dudeis en preguntar cualquier duda que surja.
0 comentarios