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


http://about.me/juanramb

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 promesa puede llamar a:


  • 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"
  1. Pierdes la separación entre entrada y salida de una función
  2. Es complicado realizar operaciones en serie o en paralelo
  3. 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