Comprendre les Promesses JavaScript

Introduction

Les Promesses en Javascript peuvent être difficiles à comprendre. Par conséquent, je souhaite noter la manière dont je comprends les promesses.

Comprendre les Promesses

Une Promesse en bref:

“Imaginez que vous êtes un enfant. Votre mère promet de vous acheter un nouveau téléphone la semaine prochaine.”

Vous ne savez pas si vous obtiendrez ce téléphone avant la semaine prochaine. Votre mère peut vraiment acheter un tout nouveau téléphone pour vous, ou elle ne le fait pas.

C’est une promesse. Une promesse a trois états. Ils sont:

  1. En attente : Vous ne savez pas si vous obtiendrez ce téléphone
  2. Réalisée : Maman est contente, elle vous achète un tout nouveau téléphone
  3. Rejetée : Maman est mécontente, elle ne vous achète pas de téléphone

Créer une Promesse

Transformons cela en JavaScript.

// ES5: Partie 1

var isMomHappy = false;

// Promise
var willIGetNewPhone = new Promise(
    function (resolve, reject) {
        if (isMomHappy) {
            var phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone); // accomplie
        } else {
            var reason = new Error('mom is not happy');
            reject(reason); // rejet
        }

    }
);

Le code est assez expressif en soi.

Voici à quoi ressemble normalement la syntaxe d’une promesse :

// la syntaxe d'une promesse ressemble à ceci
new Promise(function (resolve, reject) { ... } );

Consommation des Promesses

Maintenant que nous avons la promesse, consommons-la :

// ES5: Partie 2

var willIGetNewPhone = ... // continuer à partir de la partie 1

// appeler notre promesse
var askMom = function () {
    willIGetNewPhone
        .then(function (fulfilled) {
            // yay, tu as un nouveau téléphone
            console.log(fulfilled);
             // sortie : { marque: 'Samsung', couleur: 'noir' }
        })
        .catch(function (error) {
            // oops, maman ne l'a pas acheté
            console.log(error.message);
             // sortie : 'maman n'est pas contente'
        });
};

askMom();

Exécutons l’exemple et voyons le résultat !

Démo : https://jsbin.com/nifocu/1/edit?js,console

Chaînage des Promesses

Les promesses sont chaînables.

Disons que vous, l’enfant, promettez à votre ami que vous leur montrerez le nouveau téléphone quand votre mère vous en achètera un.

C’est une autre promesse. Écrivons-la !

// ES5

// 2ème promesse
var showOff = function (phone) {
    return new Promise(
        function (resolve, reject) {
            var message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';

            resolve(message);
        }
    );
};

Remarque : Nous pouvons raccourcir le code ci-dessus en l’écrivant comme suit :

// raccourcir

// 2ème promesse
var showOff = function (phone) {
    var message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';

    return Promise.resolve(message);
};

Chainons les promesses. Vous, l’enfant, ne pouvez commencer la promesse showOff qu’après la promesse willIGetNewPhone.

// appel de la promesse
var askMom = function () {
    willIGetNewPhone
    .then(showOff) // chaînage ici
    .then(function (fulfilled) {
            console.log(fulfilled);
         // sortie : 'Hey ami, j'ai un nouveau téléphone Samsung noir.'
        })
        .catch(function (error) {
            // oops, maman ne l'achète pas
            console.log(error.message);
         // sortie : 'maman n'est pas contente'
        });
};

Voilà comment vous pouvez chaîner la promesse.

Les promesses sont asynchrones

Les promesses sont asynchrones. Notons un message avant et après avoir appelé la promesse.

// appel de notre promesse
var askMom = function () {
    console.log('before asking Mom'); // journalisation avant
    willIGetNewPhone
        .then(showOff)
        .then(function (fulfilled) {
            console.log(fulfilled);
        })
        .catch(function (error) {
            console.log(error.message);
        });
    console.log('after asking mom'); // journalisation après
}

Quelle est la séquence de sortie attendue ? Vous pourriez vous attendre à :

1. before asking Mom
2. Hey friend, I have a new black Samsung phone.
3. after asking mom

Cependant, la séquence de sortie réelle est :

1. before asking Mom
2. after asking mom
3. Hey friend, I have a new black Samsung phone.

Vous ne vous arrêteriez pas de jouer en attendant la promesse de votre mère (le nouveau téléphone). C’est ce que l’on appelle asynchrone : le code s’exécutera sans bloquer ou attendre le résultat. Tout ce qui doit attendre qu’une promesse se réalise pour continuer est placé dans .then.

Voici l’exemple complet en ES5 :

// ES5: Exemple complet

var isMomHappy = true;

// Promise
var willIGetNewPhone = new Promise(
    function (resolve, reject) {
        if (isMomHappy) {
            var phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone); // accomplie
        } else {
            var reason = new Error('mom is not happy');
            reject(reason); // rejet
        }

    }
);

// 2ème promise
var showOff = function (phone) {
    var message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';

    return Promise.resolve(message);
};

// appeler notre promise
var askMom = function () {
    willIGetNewPhone
    .then(showOff) // chaîner ici
    .then(function (fulfilled) {
            console.log(fulfilled);
            // sortie: 'Salut mon pote, j'ai un nouveau téléphone Samsung noir.'
        })
        .catch(function (error) {
            // oups, maman ne l'achète pas
            console.log(error.message);
            // sortie: 'maman n'est pas contente'
        });
};

askMom();

Promesses en ES5, ES6/2015, ES7/Next

ES5 – La plupart des navigateurs

Le code de démonstration est utilisable dans les environnements ES5 (tous les principaux navigateurs + NodeJs) si vous incluez la bibliothèque de promesses Bluebird. C’est parce qu’ES5 ne prend pas en charge les promesses nativement. Une autre bibliothèque de promesses célèbre est Q par Kris Kowal.

ES6 / ES2015 – Navigateurs modernes, NodeJs v6

Le code de démonstration fonctionne immédiatement car ES6 prend en charge les promesses de manière native. De plus, avec les fonctions ES6, nous pouvons simplifier davantage le code avec une fonction fléchée et utiliser const et let.

Voici l’exemple complet en code ES6:

//_ ES6: Exemple complet_

const isMomHappy = true;

// Promise
const willIGetNewPhone = new Promise(
    (resolve, reject) => { // fonction fléchée
        if (isMomHappy) {
            const phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone);
        } else {
            const reason = new Error('mom is not happy');
            reject(reason);
        }

    }
);

// 2ème promesse
const showOff = function (phone) {
    const message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';
    return Promise.resolve(message);
};

// appel de notre promesse
const askMom = function () {
    willIGetNewPhone
        .then(showOff)
        .then(fulfilled => console.log(fulfilled)) // fonction fléchée
        .catch(error => console.log(error.message)); // fonction fléchée
};

askMom();

Notez que tous les var sont remplacés par const. Toutes les function(resolve, reject) ont été simplifiées en (resolve, reject) =>. Il y a plusieurs avantages qui découlent de ces modifications.

ES7 – Async/Await

ES7 a introduit la syntaxe async et await. Elle rend la syntaxe asynchrone plus facile à comprendre, sans les .then et .catch.

Réécrivons notre exemple avec la syntaxe ES7:

// ES7: Exemple complet
const isMomHappy = true;

// Promise
const willIGetNewPhone = new Promise(
    (resolve, reject) => {
        if (isMomHappy) {
            const phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone);
        } else {
            const reason = new Error('mom is not happy');
            reject(reason);
        }

    }
);

// 2ème promesse
async function showOff(phone) {
    return new Promise(
        (resolve, reject) => {
            var message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';

            resolve(message);
        }
    );
};

// appel de notre promesse en style async await ES7
async function askMom() {
    try {
        console.log('before asking Mom');

        let phone = await willIGetNewPhone;
        let message = await showOff(phone);

        console.log(message);
        console.log('after asking mom');
    }
    catch (error) {
        console.log(error.message);
    }
}

// async await ici aussi
(async () => {
    await askMom();
})();

Promesses et quand les utiliser

Pourquoi avons-nous besoin de promesses ? À quoi ressemblait le monde avant les promesses ? Avant de répondre à ces questions, revenons aux bases.

Fonction Normale VS Fonction Asynchrone

Examinons ces deux exemples. Les deux exemples effectuent l’addition de deux nombres : l’un utilise des fonctions normales, et l’autre effectue l’addition à distance.

Fonction Normale pour Additionner Deux Nombres

// additionner deux nombres normalement

function add (num1, num2) {
    return num1 + num2;
}

const result = add(1, 2); // vous obtenez result = 3 immédiatement
Fonction Asynchrone pour Additionner Deux Nombres
// additionner deux nombres à distance

// obtenir le résultat en appelant une API
const result = getAddResultFromServer('http://www.example.com?num1=1&num2=2');
// vous obtenez result = "undefined"

Si vous additionnez les nombres avec la fonction normale, vous obtenez le résultat immédiatement. Cependant, lorsque vous effectuez un appel à distance pour obtenir le résultat, vous devez attendre, et vous ne pouvez pas obtenir le résultat immédiatement.

Vous ne savez pas si vous obtiendrez le résultat car le serveur pourrait être hors ligne, lent à répondre, etc. Vous ne voulez pas que tout votre processus soit bloqué en attendant le résultat.

Appeler des API, télécharger des fichiers et lire des fichiers sont parmi les opérations asynchrones courantes que vous effectuerez.

Vous n’avez pas besoin d’utiliser des promesses pour un appel asynchrone. Avant les promesses, nous utilisions des callbacks. Les callbacks sont une fonction que vous appelez lorsque vous obtenez le résultat de retour. Modifions l’exemple précédent pour accepter un callback.

// additionner deux nombres à distance
// obtenir le résultat en appelant une API

function addAsync (num1, num2, callback) {
    // utiliser l'API de callback getJSON de jQuery
    return $.getJSON('http://www.example.com', {
        num1: num1,
        num2: num2
    }, callback);
}

addAsync(1, 2, success => {
    // callback
    const result = success; // vous obtenez result = 3 ici
});

Action Asynchrone Suivante

Au lieu d’ajouter les nombres un par un, nous voulons les additionner trois fois. Dans une fonction normale, nous ferions ceci :-

// additionner deux nombres normalement

let resultA, resultB, resultC;

 function add (num1, num2) {
    return num1 + num2;
}

resultA = add(1, 2); // vous obtenez resultA = 3 immédiatement
resultB = add(resultA, 3); // vous obtenez resultB = 6 immédiatement
resultC = add(resultB, 4); // vous obtenez resultC = 10 immédiatement

console.log('total' + resultC);
console.log(resultA, resultB, resultC);

Voici à quoi cela ressemble avec des callbacks :

// additionner deux nombres à distance
// obtenir le résultat en appelant une API

let resultA, resultB, resultC;

function addAsync (num1, num2, callback) {
    // utiliser l'API de callback getJSON de jQuery
	// https://api.jquery.com/jQuery.getJSON/
    return $.getJSON('http://www.example.com', {
        num1: num1,
        num2: num2
    }, callback);
}

addAsync(1, 2, success => {
    // callback 1
    resultA = success; // vous obtenez ici result = 3

    addAsync(resultA, 3, success => {
        // callback 2
        resultB = success; // vous obtenez ici result = 6

        addAsync(resultB, 4, success => {
            // callback 3
            resultC = success; // vous obtenez ici result = 10

            console.log('total' + resultC);
            console.log(resultA, resultB, resultC);
        });
    });
});

Démo : https://jsbin.com/barimo/edit?html,js,console

Cette syntaxe est moins conviviale en raison des callbacks imbriqués profondément.

Éviter les Callbacks Imbriqués Profondément

Les promesses peuvent vous aider à éviter les callbacks imbriqués profondément. Examinons la version avec promesses du même exemple :

// ajouter deux nombres à distance en utilisant les Observables

let resultA, resultB, resultC;

function addAsync(num1, num2) {
    // utiliser l'API fetch d'ES6, qui retourne une promesse
	// Qu'est-ce que .json()? https://developer.mozilla.org/fr/docs/Web/API/Body/json
    return fetch(`http://www.example.com?num1=${num1}&num2=${num2}`)
        .then(x => x.json()); 
}

addAsync(1, 2)
    .then(success => {
        resultA = success;
        return resultA;
    })
    .then(success => addAsync(success, 3))
    .then(success => {
        resultB = success;
        return resultB;
    })
    .then(success => addAsync(success, 4))
    .then(success => {
        resultC = success;
        return resultC;
    })
    .then(success => {
        console.log('total: ' + success)
        console.log(resultA, resultB, resultC)
    });

Avec les promesses, nous aplatissons le callback avec .then. D’une certaine manière, cela semble plus propre car il n’y a pas de callback imbriqués. Avec la syntaxe async d’ES7, vous pourriez encore améliorer cet exemple.

Observables

Avant de vous décider pour les promesses, il existe quelque chose qui est apparu pour vous aider à gérer les données asynchrones appelé Observables.

Examinons le même exemple écrit avec les Observables. Dans cet exemple, nous utiliserons RxJS pour les observables.

let Observable = Rx.Observable;
let resultA, resultB, resultC;

function addAsync(num1, num2) {
    // utiliser l'API fetch d'ES6, qui retourne une promesse
    const promise = fetch(`http://www.example.com?num1=${num1}&num2=${num2}`)
        .then(x => x.json());

    return Observable.fromPromise(promise);
}

addAsync(1,2)
  .do(x => resultA = x)
  .flatMap(x => addAsync(x, 3))
  .do(x => resultB = x)
  .flatMap(x => addAsync(x, 4))
  .do(x => resultC = x)
  .subscribe(x => {
    console.log('total: ' + x)
    console.log(resultA, resultB, resultC)
  });

Les Observables peuvent faire des choses plus intéressantes. Par exemple, delay ajoute une fonction par 3 secondes avec une seule ligne de code ou retry pour pouvoir réessayer un appel un certain nombre de fois.

...

addAsync(1,2)
  .delay(3000) // retarder de 3 secondes
  .do(x => resultA = x)
  ...

Vous pouvez lire l’un de mes articles sur RxJs ici.

Conclusion

Se familiariser avec les callbacks et les promises est important. Comprenez-les et utilisez-les. Ne vous inquiétez pas encore des Observables. Les trois peuvent intervenir dans votre développement en fonction de la situation.

Source:
https://www.digitalocean.com/community/tutorials/understanding-javascript-promises