Promesas en javascript
Juan Ramón Martín Blanco
Grupo de Usuarios de Linux
de la Universidad Carlos III de Madrid
Cursos de Marzo de 2014
Sobre mi
Trabajo como desarrollador/ingeniero de sistemas
Promesas
Una promesa representa un posible resultado:
- Realización correcta + valor asociado
- Un fallo o rechazo + motivo
Promesas
- Una función que acepta el resultado como parámetro
- ó Una función que acepta el error como parámetro
Promesas
Puede estar en uno de los siguientes estados:
- Pendiente
- Resuelta
- Rechazada
Promesas
Se garantiza que:
- Sólo una de las funciones será llamada
- A la función que se llame se le llamará con único parámetro
- Si la promesa ya ha sido resuelta, las funciones se van a llamar cuando las acoples
- Las funciones se llamarán de forma asíncrona
¿Y todo esto para qué?
- Queremos código asíncrono
- La forma más sencilla de obtenerlo es usando callbacks
- Los callbacks tienen sus "problemillas"
- Pierdes la separación entre entrada y salida de una función
- Es complicado realizar operaciones en serie o en paralelo
- Se pierde información de depuración de las excepciones
Pierdes separación entre entrada/salida de una función
function getUser(id, callback){
$.get('/api/users/'+id, function(datos){
callback(datos);
});
};
getUser(5,function(user){
console.log('Nombre: '+user.name);
})
Es complicado realizar operaciones en serie o paralelo
getUser(id, function(user){
getTwits(user.twitterId, function(twits){
muestraTwits(twits);
});
});
var twits, fbFriends, searchResults;
getTweets(user.twitterId, function(result){
twitts=result;
resultadoObtenido();
});
getFbFriends(user.fbId, function(result){
fbFriends=result;
resultadoObtenido();
});
searchGoogle(user.name, function(result){
searchResults=result;
resultadoObtenido();
});
var resultados=0;
function resultadoObtenido(){
if(++resultados === 3)
showUserDashboard(twits,fbFriends,searchResults);
};
Los errores se pierden fácilmente
function obtenerTamañoTotal(directorio, callback){
fs.readDir(directorio, function(error, archivos){
var leidos = 0;
var total = 0;
function leido(){
if (++leidos === archivos.length)
callback(total);
};
archivos.forEach(function(nombre){
fs.readFile(nombre, function(error, archivo){
total += archivo.length;
leido();
});
});
});
});
Infierno de callbacks
paso1(function(valor1){
paso2(valor1, function(valor2){
paso3(valor2, function(valor3){
paso4(valor3, function(valor4){
paso5(valor4, function(valor6){
//haz algo con valor6
});
});
});
});
});
¿Y todo esto para qué?
- Las promesas nos ayudan a resolver esos problemas
- En vez de llamar al callback devolvemos una promesa
readDir(path, function(error, fileList){
//hago cosas
});
var fileListPromise = readDir(path);
function siCumplida(fileList){
//hago cosas
}
function siRechazada(error){
//hago otras cosas
}
fileListPromise.then(siCumplida, siRechazada);
¿Y todo esto para qué?
- Podemos encadenar las promesas
var otraPromesa = unaPromesa.then(siCumplida, siRechazada);
-Si siCumplida/siRechazada devuelve un valor, la promesa otraPromesa se resolverá con ese valor:
- Si el valor es una promesa, otraPromesa adopta el estado de unaPromesa
- Si es cualquier otra cosa, otraPromesa es resuelta satisfactoriamente con ese valor
-Si siCumplida/siRechazada lanza una excepción, otraPromesa será rechazada con esa excepción
¿Y todo esto para qué?
- Podemos encadenar las promesas
-Una forma de evitar el infierno de callbacks
promesaPaso1
.then(promesaPaso2)
.then(promesaPaso3)
.then(promesaPaso4)
.then(promesaPaso5)
.then(function(valor6){
//haz algo con valor6
});
Ejemplos: Lanzando/Capturando una excepción
var user = getUser();if(user === null)throw new Error("user is null");//Con promesasvar userPromise = getUser().then(function(user){if(user === null)throw new Error("user is null");return user;});
try{updateUser(data);catch(err){console.log("Hubo un error",err);}//Con promesasvar updatePromise = updateUser(data).then(function(updatedUser){console.log('Updated user: ',updatedUser);}, function(error){ console.log('Hubo un error:',error});
Ejemplos: Relanzando una excepción
try{updateUser(data);catch(err){throw new Error('Fallo actualizar el usuario: '+err.message);}//Con promesasvar updatePromise =updateUser(data).then(undefined, function(error){throw new Error('Fallo actualizar el usuario: '+err.message);});
Ejemplos: Propagación de errores
getUser(1,function(err,user){if(err){showError(err);}else{getTwits(user, function(err,twits){if(err){showError(err);}else{showTwits(twits);}})}});//Con promesas:getUser(1).then(getTwits).then(showTwits,showError);
Ejemplos: Operaciones con varias promesas
//Es resuelta con un array de resultados, o rechazada si alguna de las promesas es rechazadaall([ getUser(1), getColour("green") ]);//Es resuelta cuando cualquiera de las promesas se resuelve, o se rechaza si todas son rechazadasany([ hazAlgoConUsuario(1), hazOtraCosaConUsuario(1) ]);//Si una funcion acepta una promesa como parámetro y otra función devuelve una promesa, las podemos combinarmiFuncion1("hola", miFuncion2("mundo"));
Cómo usar promesas en tu código
- Elige una biblioteca que cumpla la especificación Promise/A+:
Yo he probado Q y when.js
- Cuidado con las que no lo hacen
Usa Q o when para envolverlas:
var promesaDeVerdad=Q(jQueryPromise);var promesaDeverdad=when(jQueryPromise);
- Creo que jQuery en su última versión ya lo es :)
Las promesas no
- Reemplazan a los eventos
- Reemplazan a los streams
- Te dan una forma de hacer programación funcional reactiva
Promesas+eventos+streams:
- Un evento puede lanzar una función que devuelve una promesa
- Una función que hace una petición HTTP puede devolverte una promesa de un stream
Problemas:
La excepción no capturada
throw new Error('Ayyy!');var promesa = hazAlgo().then(function(){throw new Error('Ayyy!');});
Como evitarlo:
- Devuelve la promesa a quien te ha llamado
- o llama a .done() en la promesa para hacer que cualquier rechazo no controlado se lance.
dameUnaPromesa().then(function(resultado){console.log("Resultado",resultado);}).done();
Patrones: try/catch/finally
ui.startSpinner();hazCosa().then(hazOtraCosa).then(mueveResultado).catch(muestraError).finally(ui.stopSpinner).done();
Patrones: all+spread
Q.all([getUser(),getColor()]) .then(function(results){ console.log('User: ', results[0]); console.log('color: ', results[1]); }) .done();//Con spread Q.all([getUser(),getColor()]) .spread(function(user, color){ console.log('User: ', user); console.log('color: ', color); }) .done();
Patrones: map + all
var userIds=["1", "3", "40"];Q.all(userIds.map(getUserById)).then(function(users){console.log("Usuarios: ",users);}).done()
Patrones: denodeify
var readFile = Q.denodeify(fs.readFile);var readDir = Q.denodeify(fs.readDir);readDir("~").then(function(files){return files[0]}).then(readFile).then(function(data){console.log('el fichero contiene: ',data);}).catch(function(error){console.log('ocurrió un error: ',error);}).done();
Enlaces
Gracias!
Promesas en javascript
By juanramb
Promesas en javascript
- 3,138