Promises

Jesús Leganés-Combarro 'piranna'

piranna@gmail.com

Agosto 2018

Javascript

  • Asincrono y no bloqueante (recomendado)

  • Ejecución no lineal (callbacks, eventos...)
  • Ningún estandar en el lenguaje
  • Librerias externas
  • "Callback Hell"
const {mkdir, writeFile} = require('fs')

mkdir('path', function(error)
{
  if(error && error.code !== 'EEXIST') return console.trace(error)

  writeFile('path/file.txt', 'asdf', function(error)
  {
    if(error) return console.trace(error)

    console.log('Profit!')
  })
})

Que es un objeto `Promise`

const {promises: {mkdir, writeFile}} = require('fs')

mkdir('path')
.catch(function({code})
{
  if(code !== 'EEXIST') throw error
})
.then(function()
{
  return writeFile('path/file.txt', 'asdf')
})
.then(function()
{
  console.log('Profit!')
},
console.trace)
  • Incorporado al lenguaje
  • Futures, Deferred...
  • Envuelve un resultado, avisando cuando se haya producido

Estados

  • pending, operación en curso
  • fullfilled, terminó correctamente
  • rejected, la operación falló

Métodos

  • then(onFullfilled, onRejected)
  • catch(onRejected)
  • finally(onFinally)

Thenable objects

const thenable =
{
  then(onFullfilled)
  {
    onFullfilled('asdf')
  }
}

thenable.then(function(value)
{
  console.log(value)  // asdf
})
  • (Casi) cualquier objeto con then() puede usarse como un Promise
  • Promises / A+
  • jQuery no es compatible

Creando promesas

const promise = new Promise(function(resolve, reject)
{
  resolve('asdf')
})

promise.then(function(value)
{
  console.log(value)  // asdf
})
const promise = Promise.resolve('asdf')

promise.then(function(value)
{
  console.log(value)  // asdf
})
const promise = new Promise(function(resolve)
{
  setTimeout(resolve, 1000, 'asdf')
})

promise.then(function(value)
{
  console.log(value)  // asdf, 1 second later
})
const button = new CoolButton()

const promise = new Promise(function(resolve)
{
  button.once('click', resolve)
})

promise.then(function(value)
{
  console.log('button clicked')
})
const {mkdir, writeFile} = require('fs')
const {promisify} = require('util')

promisify(mkdir)('path')
.catch(function({code})
{
  if(code !== 'EEXIST') throw error
})
.then(function()
{
  return promisify(writeFile)('path/file.txt', 'asdf')
})
.then(function()
{
  console.log('Profit!')
},
console.trace)

Promesas encadenadas

Promise.resolve(1)
.then(function(value)
{
  console.log(value)  // 1

  return 'asdf'
}
.then(function(value)
{
  console.log(value)  // asdf

  // no devolvemos nada, la nueva promesa se resolvera con valor `undefined`
}

then() genera una nueva promesa

  • return con un valor (por defecto undefined), la promesa resuelve
  • throw una excepción, la promesa es rechazada
  • return una promesa, se convierte en la promesa devuelta (completada o rechazada)
const promise1 = Promise.resolve(1)

const promise2 = promise1.then(function(value)
{
  console.log(value)  // 1

  return 'asdf'
}
.then(function(value)
{
  console.log(value)  // asdf
}

promise1.then(function(value)
{
  console.log(value)  // 1 again, since `promise1` now has two binded callbacks
})
  • Usar varios then() consecutivos NO vincula los callbacks a la promesa original, sino cada vez genera una nueva
  • para vincular varios callbacks a una misma promesa, hacerlo directamente sobre ella

Control de flujo

const {promises: {mkdir, writeFile}} = require('fs')

mkdir('path')
.catch(function({code})
{
  if(code !== 'EEXIST') throw error
})
.then(function()
{
  return writeFile('path/file.txt', 'asdf')
})
.then(function()
{
  console.log('Profit!')
},
console.trace)
Promise.all([
  Promise.resolve(1),
  2,
  fetch('http://www.google.es')
    .then(res => res.text())
])
.then(function([value1, value2, value3])
{
  console.log(value1)  // 1
  console.log(value2)  // 2
  console.log(value3)  // <html>... </html>
})
Promise.race([
  fetch('http://www.google.es')
    .then(res => res.text()),
  new Promise(function(resolve, reject)
  {
    setTimeout(reject, 5000, 'timeout')
  })
])
.then(function(html)
{
  console.log(html)  // <html>... </html>
},
function(error)
{
  if(error !== 'timeout') throw error
  
  console.log('Request timed out')
})
  • all(array), resuelve a un array con los resultados de todas las promesas del array de entrada, o el error de la primera promesa que haya sido rechazada
  • race(array), devuelve la primera promesa que haya sido resuelta o rechazada
function print23()
{
  return new Promise(function(resolve)
  {
    setTimeout(function()
    {
      console.log(2)

      resolve(3)
    }, 1000)
  })
}

function func()
{
  console.log(1)

  console.log(print23())
  
  console.log(4)

  return 5
}

console.log(func())  // 1, [object Promise], 4, 5, 2
async function func()
{
  console.log(1)

  console.log(await print23())
  
  console.log(4)

  return 5
}

func().then(console.log)  // 1, 2, 3, 4, 5

Async / Await

function func()
{
  console.log(1)

  return print23()
  .then(function(three)
  {
    console.log(three)

    console.log(4)

    return 5
  })
}

func().then(console.log)  // 1, 2, 3, 4, 5
  • apariencia de código lineal en operaciones asincronas
  • async, envuelve una función y devuelve una promesa que resuelve a su valor retornado
  • await, para la ejecución de una función para continuarla después

Promises

By Jesús Leganés-Combarro