Javascript promises

Les promesses

Les promesses

"Une promesse est une abstraction logicielle qui rend plus agréable le fait de travailler avec du code asynchrone"

Domenic Denicola, co-creator of Q

Les promesses

En javascript

  • Les promesses sont bien plus anciennes que JS,
  • Les promesses sont natives en JavaScript depuis ES6,
  • Supportées par Chrome 32, Opera 19, Firefox 29, Safari 8 & Microsoft Edge,
  • Il existe un polyfill sinon !
  • Les promesses ES6 sont "A+ compliant"

Specification (+ compliance test) : promisesaplus.com

Promise/A+

Implémentations

Autres librairies compatibles Promise/A+ :

L'asynchrone ?

Callback hell ?

Sans promesses

"Callback hell" 😱

Pyramid of Doom

Avec des promesses

C'est déjà plus lisible ! 🙂

var floppy = require('floppy');

floppy.load('disk1')
  .then((data1) => floppy.prompt('Please insert disk 2'))
  .then(() => floppy.load('disk2'))
  .then((data2) => floppy.prompt('Please insert disk 3'))
  .then(() => floppy.load('disk3'))
  .then((data3) => floppy.prompt('Please insert disk 4'))
  .then(() => floppy.load('disk4'))
  .then((data4) => floppy.prompt('Please insert disk 5'))
  .then(() => floppy.load('disk5'))
  .then((data4) => floppy.prompt('OK!'));

Si floppy.load() et floppy.prompt() retournent une promesse

Pour simplifier

floppy.load("disk1", function (err, results) {
  // the rest of your code goes here.
});
floppy.load("disk1").then(function() {
  // the rest of your code goes here.
});

devient (si load() retourne un "then-able") : 

Sans promesse :

Coder un "thenable" simple !

Terminologie

  • fulfilled - L'action associée à la promesse a réussi.
  • rejected - L'action associée à la promesse a échoué.
  • pending - N'a pas encore réussi ou échoué.
  • settled - A réussi ou échoué.

 

Déclarer une promesse (ES6)

Resolve & Reject

floppy.load = new Promise(function(resolve, reject) {
  // faire un truc, peut-être asynchrone, puis…

  if (/* tout a bien marché */) {
    resolve("Ces trucs ont marché !"); // <== FULFILLED
  }
  else {
    reject(Error("Ça a foiré")); // <== REJECTED
  }
});

Déclarer une promesse (ES6)

Déclaration :

Utilisation :

getJSON('story.json').then(function getChapter1(story) {
  // [...] display chapter 1 & returns a promise
}).then(function getChapter2() {
  // [...] display chapter 2 & returns a promise
}).then(function getChapter3() {
  // [...] display chapter 3
})

Sequencing: "chainer les then"

Chainer les "then"

/**
 * Generates a promise adding 10 to a value after 1 sec
 */
function getPromise(value) {
  return new Promise(function(resolve, reject) {
    setTimeout(function() { resolve(value + 10); }, 1000);
  });
}

/**
 * Promises sequence
 */
getPromise(0)
  .then(result => getPromise(result))
  .then(result => getPromise(result))
  .then(result => {
    console.log(result);
  });

Que vaut "result" à la fin ?

cf. http://bevacqua.github.io/promisees

Attraper les erreurs

catch

new Promise(function(resolve, reject) {
  setTimeout(function() { reject('ERR!'); }, 3000);
})
.then(function(e) { console.log('done', e); })
.catch(function(e) { console.log('catch: ', e); });

// From the console:
// 'catch: ERR!'

cf. http://bevacqua.github.io/promisees

Errors chaining

var log = "";
 
function doWork() {
    log += "W";
    return Promise.resolve();
}
 
function doError() {
    log += "E";
    throw new Error("oops!");
}
 
function errorHandler(error) {
    log += "H";
}
doWork()
    .then(doWork)
    .then(doError)
    .then(doWork)
    .then(doWork, errorHandler)
    .then(verify);
     
  function verify() {
    console.log(log);
    done();
}

1) Lors d'une exception, les "then" suivants ne sont pas exécutés jusqu'à tomber sur un "catch".

2) Après un "catch", on reprend la chaine classique (on repasse dans le "then").

Que vaut "log" ?

En parallèle

var request1 = fetch('/users.json');
var request2 = fetch('/articles.json');

Promise.all([request1, request2]).then(function(results) {
  // Both promises done!
});

Promise.all

getJSON('story.json').then(function(story) {
  return Promise.all([getChapter1(), getChapter2(), getChapter3()])
}).then(function(results) {
  // Display chapter 1, 2 & 3
});

En parallèle

const p1 = new Promise((resolve, reject) => { 
  setTimeout(resolve, 1000, "one"); 
});

const p2 = new Promise((resolve, reject) => { 
  setTimeout(resolve, 2000, "two"); 
});

const p3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 3000, "three");
});

Promise.all([p1, p2, p3]).then(value => { 
  console.log(value);
}, reason => {
  console.log(reason)
});

En parallèle

const p1 = new Promise(function(resolve, reject) { 
  setTimeout(resolve, 500, "one"); 
});

const p2 = new Promise(function(resolve, reject) { 
  setTimeout(resolve, 100, "two"); 
});

Promise.race([p1, p2]).then(function(value) {
  console.log(value); // "two"
  // Both resolve, but p2 is faster
});

Et aussi, "Promise.race"

On récupère ainsi la première qui est "fulfilled"

Le futur ?

https://pouchdb.com/2015/03/05/taming-the-async-beast-with-es7.html

Le futur !

getArticleId()
  .then(id => getArticleContent(id))
  .then(content => console.log(content));
getArticleId() et getArticleContent() retournent une Promise

Sachant que :

avec Async/Await

Avec une séquence de "then" :

// Await ne marchera que dans une fonction marquée async
async function getMyArticle() {
  const id = await getArticleId();
  const content = await getArticleContent(id);
  console.log(content);

  return content;
}

Avec async/await :

Chaining ?

avec Async/Await

async function getMyArticle() {
  const id = await getArticleId();
  const content = await getArticleContent(id);
  return content;
}

On peut l'apeller avec un autre async 

async function read() {
  var content = await getMyArticle();
  console.log(content);
}

Ou l'utiliser comme une promesse

getMyArticle().then(content => console.log(content));

Concurrency ?

avec Async/Await

async function concurrent () {
  const [r1, r2, r3] = await Promise.all([p1, p2, p3]);
}

Errors catching ?

avec Async/Await

async function getMyArticle() {
  const id = await getArticleId();
  if (id === null) {
    throw 'err';
  } else {
    return content;
  } 
}

Déjà vu ?

async function read() {
  try {
    var content = await getMyArticle();
  } catch (err) {
    console.error(err);
  }
}

Quand ?

Async/Await

Promises

exercice, vite fait !

Avec Babel REPL, écrire une suite de promesses pour :

  • récupérer les acteurs d'un film sur http://www.omdbapi.com
  • attendre 2 secondes
  • afficher les acteurs triés par ordre alphabétique.

Javascript promises

By Julien Herpin

Javascript promises

  • 1,310