Aller au contenu

JavaScript asynchrone

Asynchrone : fait référence à un environnement de communication où le client ne doit pas attendre une réponse immédiate pour continuer son traitement. Le fil d'exécution principal n'est pas bloqué en attendant une réponse.

JavaScript asynchrone

De nombreuses fonctionnalités des API Web utilisent désormais du code asynchrone pour s'exécuter, en particulier celles qui accèdent à un type de ressource ou le récupèrent à partir d'un périphérique externe, par exemple:

  • en récupérant un fichier sur le réseau
  • en accédant à une base de données et en renvoyant des données
  • en accédant à un flux vidéo à partir d'une webcam
  • en diffusant l'affichage vers un casque VR.

Fonctions de rappel asynchrones

Les callbacks asynchrones ou fonctions de rappels asynchrones sont des fonctions qui sont passées comme arguments lors de l'appel d'une fonction qui commencera à exécuter du code en arrière-plan. Lorsque le code d'arrière-plan a fini de s'exécuter, il appelle la fonction de rappel pour vous faire savoir que le travail est terminé. L'utilisation des callbacks est un peu démodée aujourd'hui, mais vous les verrez encore dans un certain nombre d'API plus anciennes encore couramment utilisées.

Promesses (Promises)

Les promesses sont un style de code asynchrone Javascript que vous verrez utilisé dans les API Web plus récentes.

L'objet Promise (pour « promesse ») est utilisé pour réaliser des traitements de façon asynchrone. Une promesse représente une valeur qui peut être disponible maintenant, dans le futur ou indiquée comme impossible à fournir. La "promesse" est donc de fournir une valeur ultérieurement ou de rejeter avec une raison (erreur), mais en tous les cas revenir à l'appelant plus tard.

Une Promise est dans un de ces états :

  • pending (en attente) : état initial, la promesse n'est ni remplie, ni rompue ;
  • fulfilled (tenue) : l'opération a réussi ;
  • rejected (rompue) : l'opération a échoué ;
  • settled (acquittée) : la promesse est tenue ou rompue mais elle n'est plus en attente.

Une promesse en attente peut être tenue avec une valeur ou rompue avec une raison (erreur). Quand on arrive à l'une des deux situations, les gestionnaires associés lors de l'appel de la méthode then sont alors appelés.

javascript_async.png

Gérer les opérations asynchrones avec élégance grâce aux promesses

Essentiellement, une promesse est un objet qui représente un état intermédiaire d'une opération - en fait, c'est une promesse qu'un résultat d'une certaine nature sera retourné à un moment donné dans le futur. Il n'y a aucune garantie du moment exact où l'opération se terminera, mais il est garanti que lorsque le résultat est disponible, ou que la promesse échoue, le code que vous fournissez sera exécuté afin de faire autre chose avec un résultat réussi, ou de gérer gracieusement un cas d'échec.

L'une des utilisations les plus courantes des promesses concerne les API web qui doivent accéder à des ressources externes.

Le problème des fonctions de rappel

Code désordonné et difficile à lire :

callback.js
function choisirIngredients(callback, gererErreur) {
    // Simulation d'une opération asynchrone
    setTimeout(function() {
        // Supposons que tout s'est bien passé et que nous avons les ingrédients
        const ingredients = ['tomate', 'fromage', 'pepperoni'];
        callback(ingredients);
    }, 1000);
}

function placerLaCommande(ingredients, callback, gererErreur) {
    // Simulation d'une opération asynchrone
    setTimeout(function() {
        // Supposons que tout s'est bien passé et que la commande est prête
        const commande = `Commande de pizza avec ${ingredients.join(', ')}`;
        callback(commande);
    }, 1000);
}

function ramasserLaCommande(commande, callback, gererErreur) {
    // Simulation d'une opération asynchrone
    setTimeout(function() {
        // Supposons que tout s'est bien passé et que la pizza est prête à être ramassée
        const pizza = `Pizza prête: ${commande}`;
        callback(pizza);
    }, 1000);
}
function mangerLaPizza(pizza) {
    console.log(pizza);
}

function gererErreur(erreur) {
    console.log('Erreur : ', erreur);
}

// Appel enchaîné des fonctions de rappel - "callback hell"
choisirIngredients(function(ingredients) {
    placerLaCommande(ingredients, function(commande) {
        ramasserLaCommande(commande, function(pizza) {
            mangerLaPizza(pizza);
        }, gererErreur);
    }, gererErreur);
}, gererErreur);

Créer une fonction qui retourne une promesse

Prenons par exemple l'extrait de code suivant. J'ai une fonction qui retourne un string, un affichage du résultat de la fonction à la console et un dernier affichage à la console.

function maFonction() {
    return "Tout va bien";
}

console.log(maFonction());
console.log('Fin du traitement');

// Résultat à la console
// > Tout va bien
// > Fin du traitement

Maintentant si la fonction maFonction est longue à exécuter et qu'on ne veux pas attendre le résultat pour continuer le traitement on peut transformer la fonction en fonction asynchrone qui retourne une promesse. Il faut que la fonction retourne un objet Promise. La promesse prend en paramêtre une fonction de rappel où on va indiquer avec si la promesse est tenue (resolve()) ou rompue (reject())

promise.js
function maFonction() {
    // On retourne un nouvel objet promise
    return new Promise((resolve, reject) => {

        try {
            traitement();
            // Si tout va bien on retourne une valeur avec resolve()
            resolve("Tout va bien");
        } catch (error) {
            // Si une erreur survient, on indique que l'opération a échoué avec reject()
            reject(error);
        }
    });
}

// Maintenant quand on lance la fonction on peut effectuer du traitement lorsque 
// la promesse est tenue avec **then()** ou lorsqu'il y a une erreur avec **catch()**.
// C'est deux fonctions prennent une fonction de rappel en paramêtre.
maFonction()
.then((resultat) => {
    console.log(resultat);
})
.catch((erreur) => {  
    console.log(erreur);
})

// Cette ligne de code va être exécuté sans attendre la résolution de la promesse, 
// elle peut donc être affiché avant le message dans la promesse.
console.log('Fin du traitement');

// Résultat à la console
// > Fin du traitement
// > Tout va bien

Chaîne de promesses

Amélioration de l'exemple plus haut (pizza) avec les promesses :

promise.js
function placerLaCommande(ingredients) {
    return new Promise((resolve, reject) => {
        // Simulation d'une opération asynchrone
        setTimeout(function() {
            // Supposons que tout s'est bien passé et que la commande est prête
            const commande = `Commande de pizza avec ${ingredients.join(', ')}`;
            resolve(commande);
        }, 1000);
    });
}

[..]

choisirIngredients()
    .then(function(ingredients) {
        console.log('Ingrédients choisis : ', ingredients);
        return placerLaCommande(ingredients);
    })
    .then(function(commande) {
        console.log('Commande placée : ', commande);
        return ramasserLaCommande(commande);
    })
    .then(function(pizza) {
        console.log('Pizza ramassée : ', pizza);
        mangerLaPizza(pizza);
    })
    .catch(gererErreur);

Vidéo explicative sur callback vs Promise (Bro Code)

https://www.youtube.com/watch?v=NOzi4wBHn0o

Async / Await - approche la plus récente et élégante pour l'asynchrone en JavaScript

On peut attendre le résultat d'une fonction asynchrone qui retourne une promesse à l'aide de await. Dans ce cas on va ajouter l'instruction async à la fonction asynchrone et await juste devant son appel.

Quand la promesse est rejetée une exception est levée et on peut la capturer avec un bloc try...catch.

promise.js
async function maFonction() {
    // On retourne un nouvel objet promise
    return new Promise((resolve, reject) => {

        // Mon traitement
        try {
            // Si tout va bien on retourne une valeur avec Resolve()
            resolve("Tout va bien");
        } catch (error) {
            // Si une erreur survient, on indique que l'opération a échoué avec reject()
            reject(error);
        }
    });
}

// Avec await on attend la resolution de la fonction
try {
    const resultat = await maFonction();
    console.log(resultat);
} catch (erreur) {
    console.log(erreur);
}


// Cette ligne de code va étre exécuté aprés la resolution de la fonction   
console.log('Fin du traitement');

// Résultat à la console
// > Tout va bien
// > Fin du traitement

Vidéo (suite) explicative sur async/await (Bro Code)

https://www.youtube.com/watch?v=9j1dZwFEJ-c