web-dev-qa-db-fra.com

Comment sortir correctement d'une chaîne de promesses?

Basé sur la question ici: jQuery enchaînant et en cascade alors et quand et la réponse acceptée, je veux rompre la chaîne de promesses à un moment mais je n'ai pas encore trouvé la bonne façon. Il y a plusieursmessagesà propos cela, mais je suis toujours perdu.

En prenant l'exemple de code de la question d'origine:

Menus.getCantinas().then(function(cantinas){ // `then` is how we chain promises
    Menus.cantinas = cantinas;
    // if we need to aggregate more than one promise, we `$.when`
    return $.when(Menus.getMeals(cantinas), Menus.getSides(cantinas));
}).then(function(meals, sides){ // in jQuery `then` can take multiple arguments
    Menus.sides = sides; // we can fill closure arguments here
    Menus.meals = meals;
    return Menus.getAdditives(meals, sides); // again we chain
}).then(function(additives){
    Menus.additives = additives;
    return Menus; // we can also return non promises and chain on them if we want
}).done(function(){ // done terminates a chain generally.
     // edit HTML here
});

Comment pourrais-je briser la chaîne si cantinas.length == 0? Je ne voudrais pas prendre les repas, ni les additifs, franchement je voudrais appeler une sorte de rappel "résultat vide". J'ai essayé ce qui suit qui est très moche (mais fonctionne ...). Enseignez-moi la bonne façon. C'est toujours un résultat valide, donc pas un "échec" en soi, juste un résultat vide je dirais.

var emptyResult = false;
Menus.getCantinas().then(function(cantinas){
    Menus.cantinas = cantinas;
    if (cantinas.length == 0) {
      emptyResult = true;
      return "emptyResult"; //unuglify me
    }
    return $.when(Menus.getMeals(cantinas), Menus.getSides(cantinas));
}).then(function(meals, sides){ 
    if (meals == "emptyResult") return meals;  //look at my ugliness...
    Menus.sides = sides;
    Menus.meals = meals;
    return Menus.getAdditives(meals, sides);
}).then(function(additives){
    if (additives == "emptyResult") return additives;
    Menus.additives = additives;
    return Menus;
}).done(function(){
   if (emptyResult)
     //do empty result stuff
   else
     // normal stuff
});
32
Dennis G

Premièrement, je pense qu'il vaut mieux dire que vous cherchez à "contourner" (une partie de) la chaîne des promesses plutôt qu'à la "briser".

Comme vous le dites, tester "emptyResult" à plusieurs endroits est assez moche. Heureusement, un mécanisme plus élégant est disponible tout en adhérant au même principe général de ne pas exécuter une partie de la chaîne de promesses.

Un autre mécanisme consiste à utiliser le rejet de promesse pour contrôler le flux, puis à détecter à nouveau la ou les conditions d'erreur spécifiques plus tard dans la chaîne, et à la remettre sur la voie du succès.

Menus.getCantinas().then(function(cantinas) {
    Menus.cantinas = cantinas;
    if(cantinas.length == 0) {
        return $.Deferred().reject(errMessages.noCantinas);
    } else {
        return $.when(Menus.getMeals(cantinas), Menus.getSides(cantinas));
    }
}).then(function(meals, sides) {
    Menus.sides = sides;
    Menus.meals = meals;
    return Menus.getAdditives(meals, sides);
}).then(function(additives) {
    Menus.additives = additives;
    return Menus;
}).then(null, function(err) {
    //This "catch" exists solely to detect the noCantinas condition 
    //and put the chain back on the success path.
    //Any genuine error will be propagated as such.
    //Note: you will probably want a bit of safety here as err may not be passed and may not be a string.
    return (err == errMessages.noCantinas) ? $.when(Menus) : err;
}).done(function(Menus) {
    // with no cantinas, or with everything
});

var errMessages = {
    'noCantinas': 'no cantinas'
};

Sur le plan positif, je trouve que le manque d'imbrication permet une meilleure lisibilité du chemin de réussite naturel. De plus, pour moi au moins, ce schéma nécessiterait un minimum de jonglage mental pour s'adapter à d'autres contournements, si nécessaire.

En revanche, ce modèle est légèrement moins efficace que celui de Bergi. Alors que la voie principale a le même nombre de promesses que celle de Bergi, la cantinas.length == 0 le chemin en requiert un de plus (ou un par contournement si plusieurs contournements ont été codés). En outre, ce modèle nécessite une nouvelle détection fiable des conditions d'erreur spécifiques - d'où l'objet errMessages - que certains peuvent trouver nuisibles.

14
Roamer-1888

On dirait que vous voulez branche , ne pas casser - vous voulez continuer comme d'habitude à le done. Une belle propriété de promesses est qu'elles non seulement enchaînent, mais peuvent également être imbriquées et non imbriquées sans restrictions. Dans votre cas, vous pouvez simplement mettre la partie de la chaîne que vous souhaitez "casser" à l'intérieur de votre instruction if-:

Menus.getCantinas().then(function(cantinas) {
    Menus.cantinas = cantinas;

    if (cantinas.length == 0)
        return Menus; // break!

    // else
    return $.when(Menus.getMeals(cantinas), Menus.getSides(cantinas))
    .then(function(meals, sides) {
        Menus.sides = sides;
        Menus.meals = meals;
        return Menus.getAdditives(meals, sides);
    }).then(function(additives) {
        Menus.additives = additives;
        return Menus;
    });
}).done(function(Menus) {
    // with no cantinas, or with everything
});
20
Bergi

Pour les personnes utilisant des promesses de navigateur intégrées et cherchant un moyen d'arrêter la chaîne de promesses sans informer tous les consommateurs du cas de rejet, en déclenchant les then ou catches chaînés ou en lançant n'importe quelle fonction Uncaught (in promise) erreurs, vous pouvez utiliser les éléments suivants:

var noopPromise = {
  then: () => noopPromise, 
  catch: () => noopPromise
}

function haltPromiseChain(promise) {
  promise.catch(noop)

  return noopPromise
}

// Use it thus:
var p = Promise.reject("some error")
p = haltPromiseChain(p)
p.catch(e => console.log(e)) // this never happens

Fondamentalement, noopPromise est une interface de promesse de base tronquée qui prend les fonctions de chaînage, mais n'en exécute jamais. Cela repose sur le fait qu'apparemment, le navigateur utilise la frappe de canard pour déterminer si quelque chose est une promesse, donc YMMV (j'ai testé cela dans Chrome 57.0.2987.98), mais si cela devient un problème, vous pourrait probablement créer une instance de promesse réelle et neutraliser ses méthodes then et catch.

9
jstaab