Qu'est-ce que le modèle de construction de promesse explicite et comment puis-je l'éviter?

517

J'écrivais du code qui fait quelque chose qui ressemble à ceci:

function getStuffDone(param) {           | function getStuffDone(param) {
    var d = Q.defer(); /* or $q.defer */ |     return new Promise(function(resolve, reject) {
    // or = new $.Deferred() etc.        |     // using a promise constructor
    myPromiseFn(param+1)                 |         myPromiseFn(param+1)
    .then(function(val) { /* or .done */ |         .then(function(val) {
        d.resolve(val);                  |             resolve(val);
    }).catch(function(err) { /* .fail */ |         }).catch(function(err) {
        d.reject(err);                   |             reject(err);
    });                                  |         });
    return d.promise; /* or promise() */ |     });
}                                        | }

Quelqu'un m'a dit que cela s'appelait respectivement "l' anti-motif différé " ou "l' Promiseanti-motif constructeur ", qu'est-ce qui ne va pas avec ce code et pourquoi est-il appelé un anti - motif ?

Benjamin Gruenbaum
la source
Puis-je confirmer que la suppression de ceci est (dans le contexte de la droite, pas de la gauche, par exemple) supprimer le getStuffDonewrapper de fonction et simplement utiliser le littéral Promise?
Le Dembinski
1
ou le fait d'avoir le catchbloc dans l' getStuffDoneemballage est-il l'anti-modèle?
Le Dembinski
1
Au moins pour l' Promiseexemple natif, vous avez également des wrappers de fonctions inutiles pour les gestionnaires .thenet .catch(c'est-à-dire que cela pourrait être le cas .then(resolve).catch(reject)). Une tempête parfaite d'anti-modèles.
Noah Freitas
6
@NoahFreitas ce code est écrit de cette façon à des fins didactiques. J'ai écrit cette question et réponse afin d'aider les gens qui rencontrent ce problème après avoir lu beaucoup de code ressemblant à ça :)
Benjamin Gruenbaum
Voir aussi stackoverflow.com/questions/57661537/… pour savoir comment éliminer non seulement la construction explicite de Promise, mais aussi l'utilisation d'une variable globale.
David Spector

Réponses:

357

L' antipattern différé (maintenant anti-modèle de construction explicite) inventé par Esailija est un anti-modèle commun qui est nouveau pour les promesses, je l'ai fait moi-même lorsque j'ai utilisé les promesses pour la première fois. Le problème avec le code ci-dessus est qu'il ne parvient pas à utiliser le fait que la chaîne de promesses.

Les promesses peuvent être enchaînées avec .thenet vous pouvez retourner des promesses directement. Votre code getStuffDonepeut être réécrit en:

function getStuffDone(param){
    return myPromiseFn(param+1); // much nicer, right?
}

Les promesses consistent à rendre le code asynchrone plus lisible et à se comporter comme du code synchrone sans cacher ce fait. Les promesses représentent une abstraction sur la valeur d'une opération ponctuelle, elles font abstraction de la notion d'une déclaration ou d'une expression dans un langage de programmation.

Vous ne devez utiliser des objets différés que lorsque vous convertissez une API en promesses et ne pouvez pas le faire automatiquement, ou lorsque vous écrivez des fonctions d'agrégation plus faciles à exprimer de cette façon.

Citant Esailija:

Il s'agit de l'anti-motif le plus courant. Il est facile de tomber dans cela lorsque vous ne comprenez pas vraiment les promesses et que vous les considérez comme des émetteurs d'événements glorifiés ou un utilitaire de rappel. Récapitulons: les promesses consistent à faire en sorte que le code asynchrone conserve la plupart des propriétés perdues du code synchrone telles que l'indentation plate et un canal d'exception.

Benjamin Gruenbaum
la source
@BenjaminGruenbaum: Je suis confiant dans mon utilisation des reports pour cela, donc pas besoin d'une nouvelle question. Je pensais juste que c'était un cas d'utilisation que vous manquiez dans votre réponse. Ce que je fais ressemble plus à l'opposé de l'agrégation, n'est-ce pas?
mhelvens
1
@mhelvens Si vous divisez manuellement une API sans rappel en une API de promesse qui correspond à la partie "conversion d'une API de rappel en promesses". L'antipattern consiste à encapsuler une promesse dans une autre promesse sans raison valable, vous n'emballez pas une promesse pour commencer donc elle ne s'applique pas ici.
Benjamin Gruenbaum,
@BenjaminGruenbaum: Ah, je pensais que les reports eux-mêmes étaient considérés comme un anti-modèle, ce qui avec Bluebird les déprécie, et vous mentionnez "convertir une API en promesses" (ce qui est également un cas de ne pas encapsuler une promesse pour commencer).
mhelvens
@mhelvens Je suppose que l' excès de motif anti-différé serait plus précis pour ce qu'il fait réellement. Bluebird a déprécié l' .defer()API dans le constructeur de promesses plus récent (et sûr), il n'a pas (en aucun cas) déprécié la notion de construction de promesses :)
Benjamin Gruenbaum
1
Merci @ Roamer-1888, votre référence m'a finalement aidé à comprendre quel était mon problème. On dirait que je créais des promesses imbriquées (non retournées) sans m'en rendre compte.
ghuroo
135

Qu'est ce qui ne va pas avec ça?

Mais le modèle fonctionne!

Quel chanceux êtes-vous. Malheureusement, ce n'est probablement pas le cas, car vous avez probablement oublié un cas de bord. Dans plus de la moitié des cas que j'ai vus, l'auteur a oublié de prendre soin du gestionnaire d'erreurs:

return new Promise(function(resolve) {
    getOtherPromise().then(function(result) {
        resolve(result.property.example);
    });
})

Si l'autre promesse est rejetée, cela se passera inaperçu au lieu d'être propagé à la nouvelle promesse (où elle serait gérée) - et la nouvelle promesse reste à jamais en attente, ce qui peut provoquer des fuites.

La même chose se produit dans le cas où votre code de rappel provoque une erreur - par exemple quand resultn'a pas de propertyet une exception est levée. Cela ne serait pas géré et laisserait la nouvelle promesse non résolue.

En revanche, l'utilisation .then()prend automatiquement en charge ces deux scénarios et rejette la nouvelle promesse lorsqu'une erreur se produit:

 return getOtherPromise().then(function(result) {
     return result.property.example;
 })

L'antipattern différé est non seulement encombrant, mais également sujet aux erreurs . L'utilisation .then()pour le chaînage est beaucoup plus sûre.

Mais j'ai tout géré!

Vraiment? Bien. Cependant, cela sera assez détaillé et copieux, surtout si vous utilisez une bibliothèque de promesses qui prend en charge d'autres fonctionnalités telles que l'annulation ou la transmission de messages. Ou peut-être le sera-t-il à l'avenir, ou vous voulez échanger votre bibliothèque contre une meilleure? Vous ne voudrez pas réécrire votre code pour cela.

Les méthodes des bibliothèques ( then) ne supportent pas seulement nativement toutes les fonctionnalités, elles peuvent également avoir certaines optimisations en place. Leur utilisation rendra probablement votre code plus rapide, ou du moins permettra d'être optimisé par les futures révisions de la bibliothèque.

Comment l'éviter?

Donc, chaque fois que vous vous trouvez à créer manuellement une Promiseou Deferreddes promesses déjà existantes, vérifiez d'abord l'API de la bibliothèque . L'antipattern différé est souvent appliqué par les personnes qui voient les promesses [uniquement] comme un modèle d'observation - mais les promesses sont plus que des rappels : elles sont censées être composables. Chaque bibliothèque décente a beaucoup de fonctions faciles à utiliser pour la composition des promesses de toutes les manières imaginables, en prenant soin de toutes les choses de bas niveau que vous ne voulez pas traiter.

Si vous avez trouvé un besoin de composer certaines promesses d'une manière nouvelle qui n'est pas prise en charge par une fonction d'assistance existante, l'écriture de votre propre fonction avec des différés inévitables devrait être votre dernière option. Pensez à passer à une bibliothèque plus fonctionnelle et / ou à déposer un bogue sur votre bibliothèque actuelle. Son responsable doit être en mesure de dériver la composition des fonctions existantes, d'implémenter une nouvelle fonction d'assistance pour vous et / ou d'aider à identifier les cas limites qui doivent être traités.

Bergi
la source
Existe-t-il des exemples, autres qu'une fonction incluant setTimeout, où le constructeur pourrait être utilisé sans être considéré comme "Promise constructor anitpattern"?
guest271314
1
@ guest271314: Tout ce qui est asynchrone et qui ne renvoie pas de promesse. Bien que suffisamment souvent, vous obtenez de meilleurs résultats avec les assistants de promisification dédiés des bibliothèques. Et assurez-vous de toujours promettre au niveau le plus bas, donc ce n'est pas " une fonction incluantsetTimeout ", mais " la fonction setTimeoutelle-même ".
Bergi
"Et assurez-vous de toujours promettre au plus bas niveau, donc ce n'est pas" une fonction incluant setTimeout", mais" la fonction setTimeoutelle-même "" Peut décrire, relier aux différences, entre les deux?
guest271314
@ guest271314 Une fonction qui inclut juste un appel à setTimeoutest clairement différente de la fonction setTimeoutelle - même , n'est-ce pas?
Bergi
4
Je pense que l'une des leçons importantes ici, qui n'a pas été clairement énoncée jusqu'à présent, est qu'une promesse et son enchaîné `` alors '' représentent une opération asynchrone: l'opération initiale est dans le constructeur de la promesse et le point final éventuel est dans le `` puis 'fonction. Donc, si vous avez une opération de synchronisation suivie d'une opération asynchrone, mettez le truc de synchronisation dans la promesse. Si vous avez une opération asynchrone suivie d'une synchronisation, placez les éléments de synchronisation dans le «alors». Dans le premier cas, renvoyez la promesse d'origine. Dans le second cas, renvoyez la chaîne Promise / then (qui est également une promesse).
David Spector