Aller au contenu

Langage avancé Promises

Les promesses aident à la gestion des tâches asynchrones, et notamment en évitant le callback hell (fonctions de callback s’appelant à la chaine).

Une promesse est un objet de type Promise qui représente le résultat d’une opération asynchrone, et elle peut se trouver dans l’un des 3 états suivants :

  • pending : état initial, en attente
  • fulfilled : promesse validée (opération exécutée avec succès)
  • rejected : promesse refusée (opération échouée)
const p = new Promise((resolve, reject) => {
  // wait 100ms before resolve
  setTimeout(resolve, 100, 'Success')
})

Une promesse a deux méthodes qui permettent d’enchainer plusieurs opérations asynchrones les unes à la suite des autres :

  • then(onFulfilled, onRejected) exécute onFulfilled si la promesse est validée, ou onRejected sinon
  • catch(onRejected) exécute onRejected si la promesse est refusée

Chaque méthode retournant elle-même une promesse :

p.then(result => {
  return result + ' !!!'
})
.then(result => {
  console.log(result)
})

Lorsqu’une promesse est refusée, l’erreur est directement envoyée à la prochaine méthode then(..., onRejected) ou catch(), et donc la gestion d’erreur s’en trouve très simplifiée :

// Un appel AJAX promisifié
function ajax (url) {
  return new Promise((resolve, reject) => {
    const req = new XMLHttpRequest()
    req.open('get', url)
    req.onload = () => {
      if (req.status == 200) {
        resolve(req.response)
      } else {
        reject(new Error(req.statusText))
      }
    }
    req.error = () => {
      reject(new Error('Network error'))
    }
    req.send()
  })
}
// no errors
ajax('ajax.json').then(console.log).catch(console.error)
// file not found
ajax('404.json').then(console.log).catch(console.error)
// file found, but JSON parse fails
ajax('ajax.json').then(JSON.parse).then(console.log).catch(console.error)
// file not found, AND JSON parse will fail
ajax('404.json').then(JSON.parse).then(console.log).catch(console.error)

Il existe également Promise.all(promiseArray) pour gérer de multiples actions asynchrones en parallèle, tout en attendant la fin complète pour exécuter une tâche

const promises = []
for (var i = 0; i < 10; i++) {
  const r = Math.random() * 1000
  promises.push(new Promise((resolve, reject) => {
    setTimeout(resolve, r, 'Random p' + i + ': ' + r)
  }))
}
Promise.all(promises)
.then(promises => {
  promises.forEach(p => {
    console.log(p)
  })
})

En ES7, les promesses sont «simplifiées» grâce à async et await.

const p = new Promise((resolve, reject) => {
  // wait 100ms before resolve
  setTimeout(resolve, 100, 'Success')
})

async function foo () {
  let r = await p;
  console.log(r);
}

foo();

Exercice - Promesses#

  • Créer une fonction combineImages avec un seul paramètre: un tableau d’URL
  • Cette fonction doit charger chaque URL du tableau afin d’effectuer le chargement asynchrone des images (avec new Image(), puis onload)
  • Dessiner chaque image dans un canvas HTML (avec ctx.drawImage(img, x, y)). x et y doivent être des positions aléatoires mais l’image doit toujours être visible entièrement
  • Insérer le canvas dans la page

On note que les images s’affichent progressivement (dégradez les performances réseaux, ou mettez un timer pour mieux voir l’effet). Si l’on veut obtenir une image finale «terminée», nous allons modifier notre code et utiliser les promesses :

  • Votre fonction doit retourner une Promesse qui contient l’image générée, au lieu de modifier directement un canvas. Attention de bien attendre le chargement de toutes les images avant de résoudre la promesse.
  • Avec le résultat de l’appel de cette fonction, créer une nouvelle image qui recevra son contenu et qui sera intégrée dans la page (ou en arrière-plan CSS d’une div)

Des images :

img img img img img

Les autres navigateurs ici : https://github.com/alrra/browser-logos/tree/master/src