00:00:00

L'asynchrone en JavaScript

  • Alex Marandon
  • Simon Bats

Notes

σύγ

syn : ensemble

Exemples : synthèse, synopsis, synchrétisme

Notes

χρονος

chronos : le temps

Exemples : chronologie, chronomètre

Notes

A-syn-chrone

Qui ne se fait pas en même temps.

  • une partie du code s'exécute maintenant
  • une partie s'exécute plus tard

Notes

Exemple : timer

log("Maintenant");

setTimeout(function() {
    log("Plus tard");
}, 2000);

log("Maintenant aussi");

Notes

Exemple : requête HTTP

var xhr = new XMLHttpRequest();
log("Maintenant");
xhr.addEventListener("load", function() {
   log("Plus tard : " + xhr.responseText);
});
xhr.open("GET", "/data/greeting.txt", true);
xhr.send(null);
log("Maintenant aussi");

Notes

Exemple : lecture d'un fichier

Code du serveur répondant à la requête précédente :

var fs = require('fs'), http = require('http');

http.createServer(function (req, res) {
  fs.readFile(__dirname + req.url, function (err, data) {
    res.writeHead(200);
    setTimeout(function() {  // Délai artificiel pour la démo
      res.end(data);
    }, 2000);
  });
}).listen(8080);

Notes

Les événements

L'exécution du code asynchrone est déclenchée par des événements.

Exemples :

  • Un descripteur de fichier est prêt pour la lecture ou l'ecriture
  • Un timer arrive à échéance
  • Un événement est créé par l'utilisateur (clic de souris)
  • Un signal est envoyé au processus

Notes

La boucle d'évéments

Algorithme très simplifié :

// Pseudo code
while (true) {  // Boucle sans fin

   // Recherche les événements actifs
   activeEvents = pollForActiveEvents(eventQueue);

   activeEvents.forEach(function(activeEvent) {
       // Exécute les fonctions associées
       activeEvent.callback();
       eventQueue.remove(activeEvent);
   });
}

Notes

La boucle d'évéments

Implémentations notables

Notes

La boucle d'évéments

Caractéristiques

  • un seul fil d'exécution : pas d'accès concurrent à la mémoire
  • scrute les descripteurs de fichiers sans bloquer à l'aide des outils spécifiques au système hôte : epoll pour Linux, kqueue pour BSD/OSX, etc.
  • gestion des timers (notion de temps)
  • gestion des événement utilisateurs (souris, clavier, etc.)
  • unité atomique d'exécution : la fonction

Notes

Exemple : timer sans délai

log("D'abord");

setTimeout(function() {
    log("Finalement");
}, 0);

log("Ensuite");

Notes

Exemple : timer sans délai

log("D'abord");

setTimeout(function() {
    log("Finalement");
}, 0);

log("Ensuite");

Notes

Fonction pour requêtes HTTP

function request(url, callback) {
  var xhr = new XMLHttpRequest();
  xhr.addEventListener("load", function() {
    callback(xhr.responseText);
  });
  xhr.open("GET", url, true);
  xhr.send(null);
}

log("Envoi de la requête");
request("/data/greeting.txt", function(data) {
  log("Réponse reçue : " + data);
});

Notes

Appels asynchrones multiples

request("/data/data1.json", function(data) {
  log(data);
  var value1 = JSON.parse(data).value;
  request("/data/data2.json", function(data) {
    log(data);
    var value2 = JSON.parse(data).value;
    request("/data/data3.json", function(data) {
      log(data);
      var value3 = JSON.parse(data).value;
      log("Résultat : " + (value1 + value2 + value3));
    });
  });
});

Notes

Appels asynchrones multiples

request("/data/data1.json", function(data) {
  var value1 = JSON.parse(data).value;
  getData2(value1);
});

function getData2(value1) {
  request("/data/data2.json", function(data) {
    var value2 = JSON.parse(data).value;
    getData3(value1, value2);
  });
}

function getData3(value1, value2) {
  request("/data/data3.json", function(data) {
    var value3 = JSON.parse(data).value;
    log("Résultat : " + (value1 + value2 + value3));
  });
}

Notes

L'enfer des callbacks

  • difficulté à suivre le déroulement du code basé sur des callbacks
  • difficulté à faire collaborer des callbacks entre elles

Notes

Des promesses

Notes

Qu'est-ce qu'une promesse ?

C'est une opération

  • unique
  • asynchrone
  • pas forcément complétée
  • attendue dans le futur
  • 3 états : en attente, satisfaite ou rejetée

Notes

Création d'une promesse

function requestPromise(url) {
  return new Promise(function(resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.addEventListener("load", function() {
      resolve(xhr.responseText);
    });
    xhr.open("GET", url, true);
    xhr.send(null);
  });
}

Notes

Utilisation d'une promesse

var value1, value2, value3;
requestPromise("/data/data1.json").then(function(data) {
  log(data); value1 = JSON.parse(data).value;
  return requestPromise("/data/data2.json");
}).then(function(data) {
  log(data); value2 = JSON.parse(data).value;
  return requestPromise("/data/data3.json");
}).then(function(data) {
  log(data); value3 = JSON.parse(data).value;
  log("Résultat : " + (value1 + value2 + value3));
});

Notes

Gestion des erreurs

var value1, value2, value3;
requestPromise("/data/data1.json").then(function(data) {
  log(data); value1 = JSON.parse(data).value;
  return requestPromise("/data/invalid.json");
}).then(function(data) {
  log(data); value2 = JSON.parse(data).value;
  return requestPromise("/data/data3.json");
}).then(function(data) {
  log(data); value3 = JSON.parse(data).value;
  log("Résultat : " + (value1 + value2 + value3));
}).catch(function(err) {
  log(err);
});

Notes

Parallélisme

Promise.all([
  requestPromise("/data/data1.json"),
  requestPromise("/data/data2.json"),
  requestPromise("/data/data3.json")
]).then(function(responses) {
    var values = responses.map(
      (text) => JSON.parse(text).value
    );
    log("Résultat : " + sum(values));
});

Notes

Polymorphisme synchrone/asynchrone

var cache = new Map(), url = "/data/data1.json";
function getWithCache(url) {
  if (cache.has(url)) {
    return Promise.resolve(cache.get(url));  // Promesse déjà résolue
  } else {
    var promise = requestPromise(url)
    promise.then(function(value) {       // 1er then
      cache.set(url, value);
    });
    return promise;
  }
}
getWithCache(url).then(function(value) { // 2ème then
    log(value);
    getWithCache(url).then(log);
});

Notes

Cas d'utilisation réel : l'API Fetch

fetch("/data/data1.json")
.then((response) => response.json())
.then((data) => log(data.value));

Notes

Parallélisme avec fetch

Promise.all([
  fetch("/data/data1.json"),
  fetch("/data/data2.json"),
  fetch("/data/data3.json")
]).then(function(responses) {
    Promise.all(
      responses.map(response => response.json())
    ).then(function(data) {
      var values = data.map((obj) => obj.value);
      log("Résultat : " + sum(values));
    });
});

Notes

Les générateurs

function *mygen() {
  yield "Bonjour";
  var name = yield "Comment t'appelles-tu ?";
  yield "Enchanté " + name + " !";
}

var it = mygen();
var yieldedValue = it.next(); // {"value":"Bonjour","done":false}
log(JSON.stringify(yieldedValue));

Notes

Les générateurs

function *mygen() {
  yield "Bonjour";
  var name = yield "Comment t'appelles-tu ?";
  yield "Enchanté " + name + " !";
}

var it = mygen();
log(it.next().value);
log(it.next().value);
log(it.next("Toto").value);   // Passage de valeur au générateur
log(JSON.stringify(it.next()));

Notes

yield

Deux significations :

  • produire (récoltes, résultat d'investissement)
  • céder (un bien, sa place)

Notes

Générateur et callback

function *main() {
  var response = yield requestWrapper("/data/greeting.txt");
  log(response);
}

function requestWrapper(url) {
  return request(url, function(data) {
    it.next(data);
  });
}

var it = main();
it.next();

Notes

Générateur et promise

function *main() {
  var response = yield requestPromise("/data/greeting.txt");
  log("Réponse : " + response);
}

var it = main();
var promise = it.next().value;
promise.then(function(response) {
  it.next(response);
});

Notes

Exécuteur de générateur

Fonction run issue de You don't know JS: Async & Performance

run(function *main() {
  var response = yield fetch("/data/greeting.txt");
  var text = yield response.text();
  log(text);
  var responses = yield Promise.all([
    fetch("/data/data1.json"),
    fetch("/data/data2.json"),
    fetch("/data/data3.json")
  ]);
  var data = yield Promise.all(
    responses.map((response) => response.json())
  );
  var numbers = data.map((item) => item.value);
  log(sum(numbers));
});

Notes

async / await

async function main() {
  var response = await fetch("/data/greeting.txt");
  var text = await response.text();
  log(text);
  var responses = await Promise.all([
    fetch("/data/data1.json"),
    fetch("/data/data2.json"),
    fetch("/data/data3.json")
  ]);
  var data = await Promise.all(
    responses.map((response) => response.json())
  );
  var numbers = data.map((item) => item.value);
  log(sum(numbers));
}

main();
  • support natif de l'exécution du générateur asynchrone
  • sémantique propre à l'asynchrone

Notes

La programmation réactive (PR)

Notes

PR - Introduction

Développer via des flux de données asynchrone.

  • L'évènementiel à son paroxisme

  • Tout devient un flux observable auquel on peut s'inscrire :
    variables, propriétés, structures de données, ...

  • Des outils de manipulation de flux déjà connus :
    merge, filter, map

Notes

PR - Les flux :

Un flux est une séquence continue d'évènements.

  • Chaque évènement peut transmettre :
    une valeur, une erreur, ou un signal de fin de flux

  • On s'inscrit alors au flux afin de réagir lors d'un évènement.

Les fonctions définis sont les observers alors que le flux lui est l'observable

Notes

Création d'un Observable

function requestObservable(url) {
  return Rx.Observable.create((observer) => {
    var xhr = new XMLHttpRequest();
    xhr.addEventListener("load", () => {
      observer.onNext(xhr.responseText);
      observer.onCompleted();
    });
    xhr.open("GET", url, true);
    xhr.send(null);
  });
}

Notes

Utilisation d'Observables

var res = 0;
var ob1 = requestObservable("/data/data1.json");
var ob2 = requestObservable("/data/data2.json");
var ob3 = requestObservable("/data/data3.json");
var merged = Rx.Observable.merge(ob1, ob2, ob3);

merged.subscribe(
  v => {
    var value = JSON.parse(v).value;
    log(value);
    res += value
  },
  e => log("Error: " + e),
  () => log("Résultats : " + res)
);

Notes

PR - Flux à émissions multiples

var service = Rx.Observable.create(observer => {
  var counter = 0;
  setInterval(() => {
    observer.onNext(counter++);
    if (counter === 3) observer.onCompleted(42);
  }, 1000);
  return;
});

var component = service.subscribe(
  function (x) {log('onNext: ' + x)},
  function (e) {log('onError: ' + e)},
  function () {log('onCompleted')}
);

Notes

PR - Principale bibliothèques

  • RxJS (implémentation Javascript de Rx, par Microsoft)
  • Bacon.js (librairie plus intuitive, mais un peu moins complète)

Notes