@Domenic a un article très complet sur les défaillances des objets différés jQuery: Vous manquez le point de promesses . Domenic met en évidence quelques échecs des promesses jQuery par rapport à d'autres, y compris Q , when.js, RSVP.js et ES6 promesses.
Je m'éloigne de l'article de Domenic en pensant que les promesses jQuery ont un échec inhérent, conceptuellement. J'essaie de donner des exemples au concept.
Je suppose qu'il y a deux problèmes avec l'implémentation de jQuery:
.then
la méthode n'est pas chaînableEn d'autres termes
promise.then(a).then(b)
jQuery appellera a
puis b
lorsque le promise
sera rempli.
Puisque .then
renvoie une nouvelle promesse dans les autres bibliothèques de promesses, leur équivalent serait:
promise.then(a)
promise.then(b)
L'autre problème semble être la gestion des exceptions, à savoir:
try {
promise.then(a)
} catch (e) {
}
L'équivalent dans Q serait:
try {
promise.then(a).done()
} catch (e) {
// .done() re-throws any exceptions from a
}
Dans jQuery, l'exception lance et fait des bulles lorsque a
ne parvient pas au bloc catch. Dans les autres promesses, toute exception dans a
serait appliquée à .done
ou .catch
ou autre capture asynchrone. Si aucun des appels d'API promesse n'attrape l'exception, il disparaît (d'où la meilleure pratique Q, par exemple d'utiliser .done
pour libérer toutes les exceptions non gérées).
Les problèmes ci-dessus couvrent-ils les problèmes liés à la mise en œuvre de promesses jQuery, ou ai-je mal compris ou manqué des problèmes?
Modifier Cette question concerne jQuery <3.0; à partir de jQuery 3.0 alpha jQuery est conforme à Promises/A +.
Mise à jour: jQuery 3.0 a résolu les problèmes décrits ci-dessous. Il est vraiment conforme à Promises/A +.
Cela dit, depuis que l'article a été écrit, jQuery a fait des efforts importants pour être plus de plaintes Promesses/Aplus et ils ont maintenant une méthode .then qui enchaîne.
Ainsi, même dans jQuery returnsPromise().then(a).then(b)
for promise, les fonctions de retour a
et b
fonctionneront comme prévu, déballant la valeur de retour avant de continuer. Comme illustré dans ce violon :
function timeout(){
var d = $.Deferred();
setTimeout(function(){ d.resolve(); },1000);
return d.promise();
}
timeout().then(function(){
document.body.innerHTML = "First";
return timeout();
}).then(function(){
document.body.innerHTML += "<br />Second";
return timeout();
}).then(function(){
document.body.innerHTML += "<br />Third";
return timeout();
});
Il n'y a aucun moyen de marquer une promesse jQuery rejetée comme "gérée", même si vous la résolvez, contrairement à catch. Cela rend les rejets dans jQuery intrinsèquement cassés et très difficiles à utiliser, rien de tel que try/catch
Synchrone.
Pouvez-vous deviner quels journaux ici? ( violon )
timeout().then(function(){
throw new Error("Boo");
}).then(function(){
console.log("Hello World");
},function(){
console.log("In Error Handler");
}).then(function(){
console.log("This should have run");
}).fail(function(){
console.log("But this does instead");
});
Si vous avez deviné "uncaught Error: boo"
, Vous aviez raison. Les promesses de jQuery ne sont pas pas sûres . Ils ne vous laisseront pas gérer les erreurs levées contrairement aux promesses/promesses Aplus. Qu'en est-il du rejet de la sécurité? ( violon )
timeout().then(function(){
var d = $.Deferred(); d.reject();
return d;
}).then(function(){
console.log("Hello World");
},function(){
console.log("In Error Handler");
}).then(function(){
console.log("This should have run");
}).fail(function(){
console.log("But this does instead");
});
Les journaux suivants "In Error Handler" "But this does instead"
- il n'y a aucun moyen de gérer le rejet d'une promesse jQuery. C'est différent du flux que vous attendez:
try{
throw new Error("Hello World");
} catch(e){
console.log("In Error handler");
}
console.log("This should have run");
Quel est le flux que vous obtenez avec les bibliothèques Promises/A + comme Bluebird et Q, et ce que vous attendez de l'utilité. C'est énorme et la sécurité des lancers est un gros argument de vente pour les promesses. Voici Bluebird agissant correctement dans ce cas .
jQuery exécutera la fonction passée immédiatement plutôt que de la différer si la promesse sous-jacente est déjà résolue, donc le code se comportera différemment selon que la promesse à laquelle nous attachons un gestionnaire à rejeté est déjà résolue. C'est effectivement libérer Zalgo et peut provoquer certains des bugs les plus douloureux. Cela crée certains des bogues les plus difficiles à déboguer.
Si nous regardons le code suivant: ( violon )
function timeout(){
var d = $.Deferred();
setTimeout(function(){ d.resolve(); },1000);
return d.promise();
}
console.log("This");
var p = timeout();
p.then(function(){
console.log("expected from an async api.");
});
console.log("is");
setTimeout(function(){
console.log("He");
p.then(function(){
console.log("̟̺̜̙͉Z̤̲̙̙͎̥̝A͎̣͔̙͘L̥̻̗̳̻̳̳͢G͉̖̯͓̞̩̦O̹̹̺!̙͈͎̞̬ *");
});
console.log("Comes");
},2000);
Nous pouvons observer que ce comportement si dangereux, le setTimeout
attend la fin du délai d'origine, donc jQuery change son ordre d'exécution parce que ... qui aime les API déterministes qui ne provoquent pas de débordements de pile? C'est pourquoi la spécification Promises/A + exige que les promesses soient toujours reportées à la prochaine exécution de la boucle d'événements.
Il convient de mentionner que les bibliothèques de promesses plus récentes et plus solides comme Bluebird (et expérimentalement quand) ne nécessitent pas .done
À la fin de la chaîne comme le fait Q car elles comprennent elles-mêmes les rejets non gérés, elles sont également beaucoup plus rapides que jQuery promesses ou promesses Q.