Développement Web

La programmation asynchrone en JavaScript

Romuald THION

Semestre pair 2022 UNC

Fonctions asynchrones et callbacks

Programmation événementielle \(\approxeq\) asynchrone

La boucle d’événéments

Boucle d’événéments Source : JavaScript Visualized: Event Loop

Exemple : les handlers onevent

const $output = document.getElementById("output");
const $input = document.getElementById("input");
const $eval = document.getElementById("eval");
function work() {
  $output.innerHTML += `${$input.value ** 2 + 1}<br/>`;
}
$eval.addEventListener("click", work);

Voir l’exemple.

Exemple : les alarmes

console.log("Start"); // (A)
setTimeout(
  // (T1)
  () => console.log("Call back #1"), // (CB1)
  0,
);
console.log("Middle"); // (B)
setTimeout(
  // (T2)
  () => console.log("Call back #2"), // (CB2)
  0,
);
console.log("End"); // (C)

NB : setTimeout(fct, 0) rend fct async.

Qu-est-ce qui s’affiche dans la console ? (exemple)

Attention au code bloquant

const s = new Date().getSeconds();
setTimeout(function () {
  console.log("Ran after " + (new Date().getSeconds() - s) + " seconds");
}, 0);
while (true) {
  if (new Date().getSeconds() - s >= 2) {
    console.log("Good, looped for 2 seconds");
    break;
  }
}

Qu-est-ce qui s’affiche dans la console ? (exemple)

Fonctions asynchrones

  • les fonctions synchrones sont bloquantes
    • le thread d’exécution attend la fin du traitement
  • les fonctions asynchrones ne sont pas bloquantes
    • l’exécution continue après l’appel
    • on doit passer en paramètre la suite du traitement qui sera déclenchée ultérieurement
      • via le handler d’un événement
      • via une paramètre appellé callback

Voir documentations sur javascript.info, MDN ou Node.js

L’omniprésence des fonctions asynchrones en JavaScript

Comment rendre une fonction work asynchrone ?

// le traitement sychrone à rendre
function work(x) {
  console.info(`work(${x})`);
  return x ** 2;
}
  • en la déclanchant avec setTimeout(fct, 0)
    • 0 ms = prochain tour de boucle d’événements
  • comment lui passer des paramètres ?
    • fct doit être une fonction, pas une valeur
    • le navigateur va appeller fct()

Ce n’est pas la meilleure façon de rendre une fonction asynchrone.

Exemple d’utilisation

// KO: pas de paramètre
setTimeout(work, 0);

// KO: typeof(work(2)) == "number"
setTimeout(work(2), 0);

// OK! avec function explicite
setTimeout(function () {
  return work(2);
}, 0);

// OK! avec fat arrow
setTimeout(() => work(2), 0);

Mais comment récupérer la valeur de retour de l’appel à work(n) ?

Tentative 1 : avec return

function work(x) {
  console.info(`work(${x})`);
  return x ** 2;
}

function async_work(n) {
  return setTimeout(() => work(n), 0);
}

const r1 = async_work(12);
console.log(r1);

Qu-est-ce qui s’affiche dans la console ? (exemple)

Tentative 2 : avec callback

function work(x, cb) {
  console.info(`work(${x})`);
  cb(x ** 2);
  console.info(`work done`);
}

function async_work(n, cb) {
  return setTimeout(() => work(n, cb), 0);
}

const r1 = async_work(3, (r) => console.log(r));

Qu-est-ce qui s’affiche dans la console ? (exemple)

Tentative 3 : enchainer les traitements

Phénomène appellé pyramid of doom ou callback hell

function async_work(n, cb) {
  return setTimeout(() => cb(n ** 2), 0);
}

async_work(3, (r1) => {
  console.log(r1);
  async_work(r1, (r2) => {
    console.log(r2);
    async_work(r2, (r3) => console.log(r3));
  });
});

Qu-est-ce qui s’affiche dans la console ? (exemple)

Conventions des callbacks Node.js

https://nodejs.org/en/knowledge/errors/what-are-the-error-conventions/

So to wrap it all up, when using callbacks, if an error comes up, then pass it as the first argument. Otherwise, pass null first, and then your return arguments. On the receiving end, inside the callback function, check if the first parameter is non-null. If it is non-null, then handle it as an error.

Définition classique en Node.js avec callbacks

function callback(error, retval) {
  if (error) {
    console.error(error);
    // traiter error, avec un throw par exemple
    return; // arrête le flot
  }
  console.log(retval);
  // traiter retval
}

asyncfunction(params, callback);

Exemple de fonctions asynchrones : Node.js

const fs = require("fs");
const mypath = "async-node-fs-readfile.js";

console.log("Lancement lecture asynchrone file...");
fs.readFile(mypath, "utf8", function (err, data) {
  if (err) {
    return console.error(err);
  }
  console.log(">>>");
  console.log(data);
  console.log("<<<");
});

console.log("...lecture lancée");

Voir exemple

Exercice avec setTimeout

  1. Reprendre l’exemple pour que quand on clique sur le bouton, le texte done soit ajouté à output1 au bout du nombre de secondes saisi dans input1
  2. Ajouter un nouveau bouton qui déclenchera n alarmes se déclenchant au bout de 1, 2, … n secondes pour afficher un décompte n, …, 2, 1, Done! dans output1.
  3. Même chose que 2. mais en utilisant setInterval
  4. Même chose que 2. mais en utilisant uniquement des alarmes de 1s. La première déclenche la seconde, qui déclenche la suivante etc.