Depuis une dizaine d’années et l’avènement d’AJAX, les développeurs web s’appuient sur des fonctions asynchrones afin de construire des pages et applications dynamiques.
Asynchrone : Qualifie des mouvements qui se font de manière décalée dans le temps.
On peut observer ces dernières années une tendance à baser les nouveautés du langage Javascript sur des modèles asynchrones — c’est le cas pour IndexedDb
et getUserMedia
par exemple — mais aussi à adapter des fonctions synchrones existantes en fonction asynchrones (comme localStorage avec localForage).
Pourquoi utiliser des fonctions asynchrones ? Car cela permet entre temps au moteur Javascript de gérer d’autre tâches (événements, affichage, interrogation de base locale, requêtes AJAX, …) et de conserver une interface réactive.
Bien que ces fonctions soient très utiles, elles demandent cependant une gestion différente des fonctions synchrones. En effet, on ne peut pas simplement retourner une valeur depuis une fonction asynchrone.
1 2 3 4 5 6 7 8 9 10 |
function get_reponse() { setTimeout(function() { return 42; }, 1000); } var reponse = get_reponse(); console.log(reponse); // undefined |
Pour illustrer cette notion, prenons un exemple issu du Guide du voyageur galactique de Douglas Adams.
Des chercheurs construisent un supercalculateur, Pensée Profonde, afin de calculer la réponse à la grande question sur la vie, l’univers et le reste.
Après 7 millions et demi d’années, Pensée Profonde peut enfin fournir sa réponse :
Voir l’exemple Pensée Profonde par Nicolas Chevobbe (@nchevobbe) sur CodePen.
Nous pouvons considérer Pensée Profonde comme un serveur, vers lequel nous envoyons une requête AJAX.
Ce que nous faisons ici, c’est que nous interrogeons Pensée Profonde, nous désactivons le bouton et indiquons que le calcul est en cours.
Puis, lorsque Pensée Profonde retourne la réponse, nous l’affichons.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
function get_reponse(callback) { setTimeout(function() { // On simule un appel ajax qui prend au plus 2 secondes callback(42); // Une fois la requête terminée, on execute le callback passé en paramètre, avec la réponse en paramètre de cette dernière }, Math.random() * 2000); } var button = document.querySelector('button'); var reponseDiv = document.querySelector('.reponse'); button.addEventListener('click', function() { reponseDiv.innerHTML = 'Calcul en cours…'; button.disabled = true; get_reponse(function(reponse) { reponseDiv.innerHTML = reponse; button.disabled = false; }); }); |
Pour récupérer la réponse de Pensée Profonde, nous sommes obligés de passer en paramètre de la fonction get_reponse
une autre fonction, qu’on appelle callback
, qui sera lancée depuis get_reponse
avec en paramètre de ce callback
la réponse obtenue.
Étant donné la complexité de la question, imaginons que Pensée Profonde soit parfois en surchauffe, et qu’il doive redémarrer.
Voir l’exemple Pensée Profonde par Nicolas Chevobbe (@nchevobbe) sur CodePen.
Essayez plusieurs fois pour arriver à déclencher une surfchauffe
Pour gérer cela, dans notre code d’appel nous devons passer une deuxième fonction, failureCallback
, qui sera lancée si un problème survient dans get_reponse
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
function get_reponse(callback, failureCallback) { setTimeout(function() { try { if (Math.random() > 0.5) { // On simule une surchauffe aléatoire throw TypeError("Surchauffe. Redemarrage de pensée profonde en cours…"); } callback(42); } catch (e) { failureCallback(e); // Il y a eu une erreur, on lance le failure callback } }, Math.random() * 2000); } get_reponse(function(reponse) { reponseDiv.innerHTML = reponse; button.disabled = false; }, function(e) { // Fonction de traitement de l’erreur reponseDiv.innerHTML = "Erreur lors du calcul : " + e.message; reponseDiv.classList.add('error'); button.disabled = false; }); |
# Problème
Avec ce fonctionnement, ce n’est pas la fonction appelante qui est responsable du traitement exécuté une fois la tâche asynchrone terminée.
Il revient à la fonction get_reponse
de lancer le callback
, au bon moment, le bon nombre de fois, avec la réponse en paramètre; ou le failureCallback
si un problème est survenu.
Au sein d’une équipe, vous n’êtes peut-être pas en charge de l’écriture de cette fonction, ou plus dangereux, peut-être que le callback
devra être déclenché par un service tiers hors de votre entreprise.
Que se passe-t-il si le callback
est appelé plusieurs fois ? Si à la fois le callback
et le failureCallback
sont appelés ?
1 2 3 4 5 6 7 8 9 |
function get_reponse(callback, failureCallback) { callback(12); // Premier lancement setTimeout(function() { callback(42); // Deuxième }, Math.random() * 2000); failureCallback("ooops"); // Lancement erreur } |
Dans notre cas, cela n’est pas grave, nous ne faisons qu’afficher une donnée. Mais imaginez que votre callback
soit une fonction qui finalise un achat et lance un paiement. N lancements = N paiements, pas sûr que le client soit satisfait.
On peut bien évidemment s’assurer que cela ne se produise pas dans le code appelant, en utilisant des variables flags que l’on passerait à true une fois un callback
appelé :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
var called = false; get_reponse(function(reponse) { if (called === true) { console.error('La fonction a dejà été appelée une fois'); return; } called = true; reponseDiv.innerHTML = reponse; button.disabled = false; }, function(e) { if (called === true) { console.error('La fonction a déjà été appelée une fois'); return; } called = true; reponseDiv.innerHTML = "Erreur lors du calcul : " + e.message; reponseDiv.classList.add('error'); button.disabled = false; }); |
Cela fonctionne, mais est loin d’être élégant et complexifie la lecture et la compréhension du code.
Il existe un objet qui va nous permettre de gérer ce genre de cas : Les Promises
# Promise
# Définition
Une Promise
est un objet, introduit par la nouvelle version de Javascript ES6 (pour EcmaScript 6, aussi appelée ES2015), qui est la représentation d’une valeur future.
c’est-à-dire une valeur qui n’est pas forcément connue au moment de la création, ce qui peut assez bien représenter une requête Ajax par exemple, mais aussi une récupération de données dans une base IndexedDb.
D’une manière générale, les Promises
vont nous permettre de nous affranchir des callbacks
et de l’inversion de contrôle qu’ils induisent (c’est la fonction appelée qui est chargée de les lancer).
L’objet Promise
contient des méthodes qui permettent de définir les traitements à effectuer une fois que la tâche, asynchrone ou non, n’est plus en attente.
1 2 3 4 |
//On assume que asyncFn retourne un objet Promise asyncFn().then(successFn,failureFn); |
Une Promise
peut avoir différents états:
- pending (en attente) : l’état initial
- resolved (tenue) : l’opération a réussi
- rejected (rompue) : l’opération a échoué
On pourrait comparer les Promises
aux listeners
sauf que :
- la
Promise
ne peut réussir ou échouer qu’une seule fois - si la
Promise
a réussi, et que vous assignez uncallback
après la réussite de celle-ci, lecallback
sera quand même appelé. Alors que si vous déclarez unlisteners
pour un événement déjà réalisé, votrecallback
ne sera jamais appelé.
Examinons la syntaxe d’une Promise
, en gardant notre exemple de Pensée Profonde :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
function get_reponse() { return new Promise(function(resolve, reject) { // On retourne une Promise setTimeout(function() { try { if (Math.random() > 0.5) { throw TypeError("Surchauffe. Redémarrage de pensée profonde en cours…"); } resolve(42); // Une fois l’appel terminé, on lance la fonction resolve avec la réponse en paramètre } catch (e) { /* * S’il y a eu une erreur dans le traitement, * on appelle la fonction reject * avec en paramètre l’erreur lancée */ reject(e); } }, Math.random() * 2000); }); } |
Et pour récupérer la valeur de cette fonction :
1 2 3 4 5 6 7 8 9 10 |
get_reponse().then(function(reponse) { reponseDiv.innerHTML = reponse; button.disabled = false; }, function(error) { reponseDiv.innerHTML = "Erreur lors du calcul : " + error.message; reponseDiv.classList.add('error'); button.disabled = false; }); |
Si l’on s’en tient à cette syntaxe, les deux méthodes se ressemblent encore très fortement.
Cependant, si pour une quelconque raison on décidait de résoudre la Promise
deux fois, voire de la résoudre puis de la rejeter, le traitement, lui, ne serait déclenché qu’une seule fois.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
function get_reponse() { return new Promise(function(resolve, reject) { setTimeout(function() { resolve(42); resolve(12); reject("oooops"); }, Math.random() * 2000); }); } get_reponse().then(function(rep) { console.log(rep); }, function(e) { console.log(e); }); // Une seule ligne affichée dans la console : 42 |
# Chaînage
Les Promises
vont aussi nous permettre d’optimiser le code et sa lecture.
Nos fonctions de gestion du succès et de l’échec se ressemblent finalement beaucoup : dans les deux cas on affiche un message puis on réactive le bouton.
Avec les Promises
, on pourrait transformer cela en :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
get_reponse().then(function(reponse) { reponseDiv.innerHTML = reponse; }, function(error) { reponseDiv.innerHTML = "Erreur lors du calcul : " + error.message; reponseDiv.classList.add('error'); }).then(function() { /* * Ce bloc est tout le temps appelé, * indépendamment du fait que la Promise ait été resolved ou rejected */ button.disabled = false; }); |
Que se passe-t-il ici ? On chaîne plusieurs fonctions à la suite :
1 2 3 4 5 6 |
donne moi la réponse PUIS affiche la réponse SINON il y a eu une erreur, affiche la et ajoute une classe css FINALEMENT, réactive le bouton |
Dans notre cas, nous chaînons une fonction synchrone à une fonction asynchrone, mais la puissance de ce mécanisme se révèle quand on chaîne plusieurs fonctions asynchrones entre elles.
Imaginons que nous souhaitions par exemple afficher trois chapitres d’une nouvelle.
Chaque chapitre pouvant être assez conséquent, nous décidons d’avoir une fonction permettant d’effectuer un appel AJAX et de récupérer un chapitre.
Cependant, nous voulons tout de même afficher les chapitres aussi rapidement que possible, et dans le bon ordre bien sûr.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
//On lance la récupération des différents chapitres var c1 = getChapter(1); var c2 = getChapter(2); var c3 = getChapter(3); c1.then(displayChapter)// Quand on a le premier chapitre, on l’affiche .then(function(){ return c2; // Puis On retourne la Promise de récupération du 2ème }) .then(displayChapter) // Puis on affiche le deuxième chapitre .then(function(){ return c3; // Puis on retourne la Promise de récupération du 3ème }) .then(displayChapter) // Puis on affiche le troisième chapitre .catch(function(err){ console.error(err); // Si il y a eu une erreur, on l’affiche }); |
# Attente de plusieurs traitements asynchrones simultanés
Là où les Promises
deviennent vraiment utiles c’est dans la gestion de traitements “parallèles”, qu’ils soient asynchrones ou non.
Prenons un cas classique d’une application qui pour être fonctionnelle a besoin d’effectuer plusieurs appels au serveur pour récupérer des données de la base.
Dans notre cas, on ne veut afficher la liste des personnes qu’une fois les différentes villes chargées.
Avec des callbacks
, on aurait pu faire :
1 2 3 4 5 6 7 8 9 10 11 12 |
get_users(function() { // On récupère les utilisateurs get_cities(function() { // Puis les villes display_users(); // Enfin, on affiche les utilisateurs display_cities(); // Et les villes }, function() { console.error("Erreur lors de la récuperation des villes"); }) }, function() { console.error("Erreur lors de la récuperation des utilisateurs"); }); |
Vu que les appels ne sont pas liés, on pourrait les lancer en parallèle :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
var users_loaded = false; var cities_loaded = false; var users = []; var cities = []; get_users(function(res) { // On récupère les utilisateurs users = res; users_loaded = true; // On passe le flag à true finish(); // Et on lance la fonction de finalisation }, failureFn); get_cities(function(res) { // On récupère les villes cities = res; cities_loaded = true; // On passe le flag à true finish(); // Et on lance la fonction de finalisation }, failureFn); function finish() { if (users_loaded && cities_loaded) { display_users(users); display_cities(cities); } } |
On utilise là aussi des flags pour vérifier ce qui a été récupéré, ainsi que des variables pour stocker les valeurs.
Nous n’avons que deux fonctions, imaginons avec dix, cela serait déjà beaucoup plus contraignant.
Les Promises
permettent de gérer ce genre de cas avec Promise.all
.
Promise.all
prend en paramètre un tableau de Promises
, puis retourne une Promise
, sur laquelle on pourra déclencher une fonction de succès ou d’échec.
1 2 3 4 5 6 7 8 9 |
Promise.all([get_users(), get_cities()]).then(function(res) { //res est un tableau qui contient les valeurs résolues de toute les Promises => [users, cities] display_users(res[0]); display_cities(res[1]); }, function(err) { // Dès qu'une Promise a été rejetée console.error(err); }) |
Nous avons ici un mécanisme totalement clair et un code très lisible sur ce qui doit être chargé.
Pour autant, les Promises
dans Promise.all
peuvent avoir leurs propres fonctions gestionnaires :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
var usersPromise = get_users(); var citiesPromise = get_cities(); usersPromise.then(function(users) { display_users(users); }, function() { console.error('Erreur lors du chargement des utilisateurs'); }); citiesPromise.then(function(cities) { display_cities(cities); }, function() { console.error('Erreur lors du chargement des villes'); }); Promise.all([usersPromise, citiesPromise]).then(function(res) { console.log('Tout les données sont disponibles'); }, function(err) { // Dès qu'une Promise a été rejetée console.error('Certaines données n'ont pas pu être récuperées'); }); |
Ce mécanisme peut se révéler très utile aussi quand le nombre de services à appeler est variable.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
var promises = []; if (needUsers) { var usersPromise = get_users(); usersPromise.then(display_users); promises.push(usersPromise); } if (needCities) { var citiesPromise = get_cities(); citiesPromise.then(display_cities); promises.push(citiesPromise); } Promise.all(promises).then(function(res) { console.log("Tout est chargé") }, function(err) { console.error(err); }); |
Ici, même si needUsers
et needCities
sont à false
, le callback
dans then
sera quand même appelé.
1 2 3 4 5 |
Promise.all([]).then(function() { console.log("ok"); // La console affiche bien "ok" }); |
# Gestion erreur
Nous avons vu précédemment que pour gérer le reject, nous passions une fonction en deuxième paramètre de then
.
1 2 3 |
get_reponse().then(successFn, failureFn) |
Mais il faut savoir que failureFn
est aussi appelée si une exception est lancée dans successFn
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
function get_reponse() { return new Promise(function(resolve, reject) { var ret = x + 2; // x n'est pas défini return ret; }); } get_reponse().then(function(res) { console.log(res); }, function(e) { console.error("get_reponse error : %o", e); }); // get_reponse error : ReferenceError: x is not defined |
Cela offre un mécanisme de gestion d’erreur intéressant, mais peut se révéler problématique.
Prenons le cas suivant :
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function get_reponse() { return new Promise(function(resolve, reject) { resolve(42); }); } get_reponse().then(function(x) { var res = x + a; // x n'est pas défini }, function(e) { console.error("get_reponse error : %o", e); }); |
À votre avis, qu’est-ce qui est affiché dans ce cas ?
Rien.
L’erreur se trouvant dans la fonction du then
, la fonction d’erreur n’est pas appelée, l’exception Javascript est automatiquement interceptée et ne remonte pas dans la console.
Pour cela, il faut chainer une autre fonction, catch
.
1 2 3 4 5 6 7 8 9 10 11 |
get_reponse().then(function(x) { var res = x + a; }, function(e) { console.error("get_reponse error : %o", e); }) .catch(function(e) { console.error("get_reponse catch : %o", e); }); // get_reponse catch : ReferenceError: x is not defined |
Le catch
captera n’importe quelle erreur ou reject déclenché dans l’ensemble des fonctions déclarées avant lui.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function get_reponse() { return new Promise(function(resolve, reject) { reject("oops"); }); } get_reponse().then(function(x) { console.log(x); }, function(e) { var res = x + a; }).catch(function(e) { console.error("get_reponse catch : %o", e); }); // get_reponse catch : ReferenceError: a is not defined |
On peut aussi directement se passer de la failureFn :
1 2 3 4 5 6 7 8 |
get_reponse().then(function(x) { console.log(x); }).catch(function(e) { console.error("get_reponse catch : %o", e); }); // get_reponse catch : "oops" |
Cela permet de gérer facilement des erreurs dans toute la chaîne d’appel de fonctions.
1 2 3 4 5 6 7 8 |
get_reponse() .then(traitementreponse) .then(stockagereponse) .catch(function() { console.error("Erreur dans la récupération, ou dans le traitement, ou dans le stockage"); }); |
La gestion des erreurs peut s’avérer délicate, avec un catch
quasimment implicite. Cependant, pour les cas où le catch
n’est pas déclaré, les outils de développement des navigateurs s’améliorent.
1 2 3 4 5 6 7 |
get_reponse().then(function(x) { var res = x + a; }, function(e) { console.error("get_reponse error : %o", e); }); |
Ainsi dans la dernière version de Chrome, notre code afficherait :
1 2 3 |
Unhandled promise rejection Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: ReferenceError: a is not defined} |
Dans Firefox, cela ne se fait pas automatiquement, mais on peut garder la Promise dans une variable et l’afficher dans un log.
1 2 3 4 5 6 7 8 9 10 |
var promise = get_reponse(); promise.then(function(x) { var res = x + a; }, function(e) { console.error("get_reponse error : %o", e); }); console.log(promise); |
On a d’abord l’affichage du bon déroulement de la Promise :
1 2 3 |
Promise { state: "fulfilled",value : 42 } |
Puis 5 secondes plus tard, l’erreur :
1 2 3 |
ReferenceError: a is not defined |
Cela n’est évidemment pas parfait, mais les outils vont sûrement s’améliorer. D’ailleurs Chrome est déjà en train de prévoir un module spécifique pour recenser l’ensemble des Promises
de l’application et leurs états (cf DevTools : State of the union 2015 – Addy Osmani).
# Exemple d’utilisation
Avec les différents mécanismes décrits ci-dessus, on peut penser à des cas d’utilisation multiples.
Nous avons notamment eu le cas sur un de nos projet récemment. Afin de calculer une valeur par rapport à des données saisies par l’utilisateur, nous effectuons une requête Ajax vers le serveur, qui interroge la base de données et renvoie le resultat. Pendant ce traitement, nous affichons un masque de chargement sur le composant, que nous cachons une fois la réponse obtenue.
1 2 3 4 5 6 7 8 9 10 11 |
button.addEventListener('click', function() { reponseDiv.innerHTML = ''; mask.style.visibility = 'visible' get_reponse(function(reponse) { reponseDiv.innerHTML = reponse; mask.style.visibility = 'hidden' }); }); |
Cela était satisfaisant, mais parfois, le traitement était si rapide que le masque ne s’affichait qu’une fraction de seconde, résultant en un « flash » de l’écran assez perturbant pour l’utilisateur.
Ce traitement pouvant être néanmoins relativement long, il nous était indispensable d’afficher ce masque.
Pour éviter le flash, nous pensions afficher le masque au minimum 1 seconde.
En utilisant une méthode classique, le code ressemblait à cela :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
button.addEventListener('click', function() { reponseDiv.innerHTML = ''; mask.style.visibility = 'visible' var minTimeElapsed = false; var gotReponse = false; setTimeout(function() { // On crée une fonction "timer" avec notre temps minimal minTimeElapsed = true; hideMaskIfAllDone(); }, 1000); get_reponse(function(reponse) { reponseDiv.innerHTML = reponse; gotReponse = true; hideMaskIfAllDone(); }); function hideMaskIfAllDone() { // si le temps minimum est passé et qu'on a une réponse if (minTimeElapsed === true && gotReponse === true) { mask.style.visibility = 'hidden'; } } }); |
On joue avec deux flags et une fonction de vérification pour s’assurer que tout soit fini et cacher notre masque.
La même chose avec les Promises
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
button.addEventListener('click', function() { reponseDiv.innerHTML = ''; mask.style.visibility = 'visible' var minTimeElapsed = new Promise(function(resolve) { setTimeout(resolve, 1000); // On déclare une Promise qui se résout au bout du temps minimum }); var reponsePromise = get_reponse(); reponsePromise.then(function(reponse) { reponseDiv.innerHTML = reponse; }); Promise.all([minTimeElapsed, reponsePromise]).then(function() { mask.style.visibility = 'hidden'; }); }); |
D’un point de vue fonctionnel, nous avons la même chose, mais la lecture du code s’en trouve facilitée.
# Futur
Les Promises
permettent une gestion native et puissante de l’asynchronicité, ce qui permet une lecture de code simplifiée.
Cela ira encore plus loin dans ES7 (pour EcmaScript 7), avec les fonctions async
.
Reprenons une de nos fonctions précédentes :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
function get_reponse() { return new Promise(function(resolve, reject) { setTimeout(function() { try { if (Math.random() > 0.5) { throw TypeError("Surchauffe. Redemarrage de Pensée Profonde en cours…"); } resolve(42); } catch (e) { reject(e); } }, Math.random() * 2000); }); } get_reponse().then(function(reponse) { reponseDiv.innerHTML = reponse; button.disabled = false; }, function(error) { reponseDiv.innerHTML = "Erreur lors du calcul : " + error.message; reponseDiv.classList.add('error'); button.disabled = false; }); |
Avec async
, notre fonction get_reponse resterait la même, mais le code appelant pourrait ressembler à :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
async function display_reponse() { var message; try { message = await get_reponse(); } catch (e) { message = e; reponseDiv.classList.add('error'); } reponseDiv.innerHTML = message; button.disabled = false; } display_reponse(); |
On déclare une fonction asynchrone avec async
, et on appose à la récupération de notre Promise
le mot clé await
.
Cela va mettre en pause la fonction jusqu’à ce que la Promise
soit terminée, et on pourra alors utiliser dans la suite du traitement la réponse de notre Promise
.
Si jamais la Promise
est rejetée, une exception est lancée, que l’on peut gérer avec un try/catch
.
Cela simplifie encore plus la lecture du code asynchrone, en le rapprochant de ce que l’on peut trouver dans du code synchrone.
ES7 est encore à l’état de discussion et est loin d’être présent dans nos navigateurs, mais vous pouvez utiliser dès aujourd’hui ces fonctions en utilisant un transpilateur comme Babel (anciennement 6to5), qui se chargera de transformer le code en quelque chose de compréhensible pour les navigateurs ES5.
# Conclusion
Les Promises
permettent de gérer de manière élégante l’asynchronicité qui est si présente dans les applications modernes. Elles sont la base pour de futurs mécanismes qui simplifieront notre code et sa compréhension, et sont déja très répandues dans le monde de NodeJS.
Les Promises
sont disponibles nativement pour Chrome (32+), Firefox (29+), IE11, Safari 7.1, et peuvent être utilisées dans les autres navigateurs via des librairies ou polyfill (exemple : https://github.com/getify/native-promise-only, qui respecte strictement les spécifications).
DON’T PANIC, les Promises
promettent un futur asynchrone radieux.
# Ressources
https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Promise
http://www.html5rocks.com/en/tutorials/es6/promises/
https://www.youtube.com/watch?v=wupXZp5khng
http://javascriptplayground.com/blog/2015/02/promises/
http://www.htmlxprs.com/post/48/understanding-async-functions-in-es7
1 Pingback