Image Principale

Découverte des promesses Javascript


Dans cet article, nous allons découvrir une fonctionnalité de Javascript assez utilisée aujourd'hui afin d'exécuter du code asynchrone sans se retrouver avec du code illisible grâce à l'utilisation de promesses, "promises" en anglais.

Contact Person Mathieu Marteau
il y a 6 mois

Une des grandes qualités du langage de script Javascript est la possibilité d'exécuter du code asynchrone non bloquant. Ainsi, dans nodejs, on pourra par exemple, faire une requête MySQL, et continuer à exécuter d'autres instructions en attendant la réponse, puis dans un deuxième temps en traitant cette réponse.

Nous avons vu dans un précédent article comment écrire facilement du code asynchrone en Javascript.

Pour nous rafraîchir la mémoire, je vous propose ce petit code qui expliquera rapidement le fonctionnement d'un code asynchrone:

console.log('premier code')

setTimeout( () => {
    console.log('code asynchrone exécuté')
},0)

console.log('second code')

Ce code retournera la réponse suivante:

premier code
second code
code asynchrone exécuté

Tester sur JsFiddle

Comme vous le voyez, le code situé dans le callback de la fonction setTimeout a été exécuté après que toutes les instructions synchrones aient été exécutées. Ce comportement est lié à la notion de pile d'appels de Javascript. Vous pouvez trouver plus d'informations sur ce comportement depuis la page MDN sur la gestion de la concurrence

Le callback Hell

Une problématique survient alors vis à vis de l'utilisation de ce code asynchrone. En effet, si on souhaite exécuter une instruction après l'exécution de ce code asynchrone, il devra résider dans ce même callback.

Et si on veut exécuter un autre code asynchrone, on va vite se retrouver avec des enchainements de callback et notre code deviendra totalement illisible:

setTimeout( () => {
    write('code asynchrone exécuté')
    setTimeout( () => {
        write('second code asynchrone')
        setTimeout( () => {
            write('troisième code asynchrone')
        },0)
    },0)
},0)

Tester sur JsFiddle

La solution: les promesses

Les promesses sont un type d'objet javascript qui va vous permettre de mieux organiser votre code asynchrone. Elles vous permettront également de mieux gérer vos erreurs.

Son fonctionnement est assez simple. Détaillons le code suivant:

let shouldResolve = true

let promise = new Promise( (resolve,reject) => {
    if (shouldResolve) {
        resolve('hello')
    } else {
        reject('something bad happened')
    }
}) 

Comme vous le voyez, on créé un objet de type Promise en utilisant new Promise(). Un objet de type Promise prend un paramètre: une fonction, qui prend elle-même deux paramètres: resolve et reject. Ces deux paramètres sont enfaite des fonctions que vous pouvez utiliser à tout moment dans le code de votre fonction de promesse et dans lesquels vous pouvez envoyer des paramètres que nous pourrons ensuite réutiliser.

Pour cet exemple, j'ai créé une variable shouldResolve qui permettra de choisir entre l'exécution de la fonction resolve() ou de la fonction reject() dans le code de notre promesse afin d'étudier son fonctionnement.

Pour récupérer la valeur entrée en paramètre dans resolve(), on utilisera la propriété then d'une promesse:

promise.then( message => {
    console.log(message)
})

Tester sur JsFiddle

Si on souhaite récupérer la valeur du paramètre de reject, on utilisera la propriété .catch:

promise.catch( error => {
    console.log(error)
})

En effet, on utilise la fonction reject() pour signifier qu'il y'a eu une erreur dans le code. On peut aussi utiliser throw error:

let shouldResolve = false

let promise = new Promise( (resolve,reject) => {
    if (shouldResolve) {
        resolve('hello world')
    } else {
        let error = "Something happened"
        throw error
    }
}) 

promise.catch( error => {
    console.log(error)  // Something happened
})

Tester sur JsFiddle

Le chainage de promesses

Il est possible que vous ne voyiez pas encore l'intérêt, mais on va y venir pour résoudre le problème que nous avons évoqués au début de cet article.

Les promesses peuvent être chainées, cela veut dire que l'on peut enchaîner plusieurs fois la propriété then à notre promesse, de plusieurs manières différentes:

La première manière, assez peu intéressante mais qui a le mérite d'être facile à comprendre, permet un chaînage simple sur un code synchrone:

promise.then( message => {
    console.log(message)
    return 'un premier message'
}).then( message => {
    console.log(message)
    return 'un autre message'
}).then( message => {
    console.log(message)
})

Comme vous le voyez, on va voir s'afficher dans la console:

Hello World
Un premier message
Un autre message

C'est la valeur retournée par la fonction de callback du then qui peut être utilisée dans le then suivant.

Tester sur JsFiddle

Mais une des forces des promesses, est qu'il est possible de retourner une autre promesse dans ce then, et c'est l'utilisation de la fonction resolve qui définira les paramètres du prochain then:

promise.then( message => {
    write(message)
    return new Promise( (resolve, reject) => {
        resolve('Ceci est un message')
    })
}).then( message => {
    write(message)
})

Tester sur JsFiddle

Résolution du problème

Passons donc à la résolution de notre problème de callback hell évoqué plus tôt. Pour rappel, tel était le code que l'on souhaite refactoriser:

setTimeout( () => {
    write('code asynchrone exécuté')
    setTimeout( () => {
        write('second code asynchrone')
        setTimeout( () => {
            write('troisième code asynchrone')
        },0)
    },0)
},0)

Commençons par écrire une fonction asynchrone qui nous évitera de réécrire un setTimeout à chaque fois, et qui retournera d'ailleurs une promesse:

function asyncWrite (text) {
    return new Promise( (resolve,reject) => {
        setTimeout( () => {
            console.log(text)
            resolve()
        },0)
    })
}

Cette fonction est chargée d'écrire dans la console Javascript de façon asynchrone. Cette fonction retourne une Promise et nous pourrons donc utiliser la propriété .then() sur cette fonction:

asyncWrite('bonsoir').then( () => {
        return asyncWrite('tout le monde')
    }).then( () => {
        asyncWrite('une dernière phrase')
    })

Comme vous le voyez, on obtiendra bien:

bonsoir
tout le monde
une dernière phrase

Tester sur JsFiddle

J'espère que vous aurez compris l'intérêt de ces promesses, et j'essaierai dans un prochain article d'aborder un cas concret: faire des requêtes HTTP asynchrones grâce à la librairie axios qui repose sur ces promesses