web-dev-qa-db-fra.com

comment rompre la chaîne de promesses

Je fais une telle promesse, 

function getMode(){
    var deferred = Promise.defer();

    checkIf('A')
    .then(function(bool){
        if(bool){
            deferred.resolve('A');
        }else{
            return checkIf('B');
        }
    }).then(function(bool){
        if(bool){
            deferred.resolve('B');
        }else{
            return checkIf('C');
        }
    }).then(function(bool){
        if(bool){
            deferred.resolve('C');
        }else{
            deferred.reject();
        }
    });

    return deferred.promise;
}

checkIf renvoie une promesse, et oui checkIf ne peut pas être modifié .

Comment sortir de la chaîne lors du premier match? (de toute autre manière que de jeter explicitement une erreur?)

12
mido

Je pense que vous ne voulez pas d'une chaîne ici. De manière synchrone, vous auriez écrit

function getMode(){
    if (checkIf('A')) {
        return 'A';
    } else {
        if (checkIf('B')) {
            return 'B';
        } else {
            if (checkIf('C')) {
                return 'C';
            } else {
                throw new Error();
            }
        }
    }
}

et voici comment cela devrait être traduit en promesses:

function getMode(){
    checkIf('A').then(function(bool) {
        if (bool)
            return 'A';
        return checkIf('B').then(function(bool) {
            if (bool)
                return 'B';
            return checkIf('C').then(function(bool) {
                if (bool)
                    return 'C';
                throw new Error();
            });
        });
    });
}

Il n'y a pas de if else- aplatissement des promesses.

3
Bergi

Toute autre manière que de jeter explicitement une erreur?

Vous devrez peut-être jeter quelque chose, mais cela ne doit pas nécessairement être une erreur.

La plupart des implémentations de promesse ont la méthode catch acceptant le premier argument comme type d'erreur (mais pas toutes, et pas la promesse ES6).

function BreakSignal() { }

getPromise()
    .then(function () {
        throw new BreakSignal();
    })
    .then(function () {
        // Something to skip.
    })
    .catch(BreakSignal, function () { })
    .then(function () {
        // Continue with other works.
    });

J'ajoute la possibilité de casser la mise en œuvre récente de ma propre bibliothèque de promesses. Et si vous utilisiez ThenFail (comme vous ne le feriez probablement pas), vous pouvez écrire quelque chose comme ceci:

getPromise()
    .then(function () {
        Promise.break;
    })
    .then(function () {
        // Something to skip.
    })
    .enclose()
    .then(function () {
        // Continue with other works.
    });
11
vilicvane

Je voudrais juste utiliser coroutines/spawns , cela conduit à beaucoup code simple

function* getMode(){
    if(yield checkIf('A'))
        return 'A';
    if(yield checkIf('B'))
        return 'B';
    if(yield checkIf('C'))
        return 'C';
    throw undefined; // don't actually throw or reject with non `Error`s in production
}

Si vous n'avez pas de générateurs, il y a toujours traceur ou 6to5.

3
simonzack

Vous pouvez utiliser return { then: function() {} };

.then(function(bool){
    if(bool){
        deferred.resolve('A');
        return { then: function() {} }; // end/break the chain
    }else{
        return checkIf('B');
    }
})

L'instruction return retourne un "then-capable", seulement que la méthode then ne fait rien . Lorsqu'elle est renvoyée d'une fonction dans then (), then () essaiera d'obtenir le résultat du thenable . then -able's "then" prend un rappel mais ne sera jamais appelé dans ce cas. Donc "then ()" retourne et le rappel pour le reste de la chaîne ne se produit pas.

3
Martin

Vous pouvez créer une fonction firstSucceeding qui renverrait la valeur de la première opération réussie ou jettera une NonSucceedingError.

J'ai utilisé les promesses ES6, mais vous pouvez adapter l'algorithme pour prendre en charge l'interface de promesse de votre choix.

function checkIf(val) {
    console.log('checkIf called with', val);
    return new Promise(function (resolve, reject) {
        setTimeout(resolve.bind(null, [val, val === 'B']), 0);
    });
}

var firstSucceeding = (function () {
    
    return function (alternatives, succeeded) {
        var failedPromise = Promise.reject(NoneSucceededError());  
        return (alternatives || []).reduce(function (promise, alternative) {
            return promise.then(function (result) {
                    if (succeeded(result)) return result;
                    else return alternative();
                }, alternative);
        }, failedPromise).then(function (result) {
            if (!succeeded(result)) throw NoneSucceededError();
            return result;
        });
     }
    
    function NoneSucceededError() {
        var error = new Error('None succeeded');
        error.name = 'NoneSucceededError';
        return error;
    }
})();

function getMode() {
    return firstSucceeding([
        checkIf.bind(null, 'A'),
        checkIf.bind(null, 'B'),
        checkIf.bind(null, 'C')
    ], function (result) {
        return result[1] === true;
    });
}

getMode().then(function (result) {
    console.log('res', result);
}, function (err) { console.log('err', err); });

1
plalx

J'arrive probablement tard dans la soirée, mais j'ai récemment posté une réponse à l'aide de générateurs et de la bibliothèque co qui répondrait à cette question (voir la solution 2):

Le code serait quelque chose comme:

const requestHandler = function*() {

        const survey = yield Survey.findOne({
            _id: "bananasId"
        });

        if (survey !== null) {
            console.log("use HTTP PUT instead!");
            return;
        }

        try {
            //saving empty object for demonstration purposes
            yield(new Survey({}).save());
            console.log("Saved Successfully !");
            return;
        }
        catch (error) {
            console.log(`Failed to save with error:  ${error}`);
            return;
        }

    };

    co(requestHandler)
        .then(() => {
            console.log("finished!");
        })
        .catch(console.log);

Vous écririez à peu près du code synchrone qui serait en réalité asynchrone! 

J'espère que ça aide!

0
Flame_Phoenix

j'aime beaucoup de réponses postées jusqu'à présent qui atténuent ce que le q readme appelle la "pyramide de Doom". dans l'intérêt de la discussion, j'ajouterai le motif que j'ai expliqué avant de chercher pour voir ce que font les autres. j'ai écrit une fonction comme

var null_wrap = function (fn) {
  return function () {
    var i;
    for (i = 0; i < arguments.length; i += 1) {
      if (arguments[i] === null) {
        return null;
      }
    }
    return fn.apply(null, arguments);
  };
};

et j’ai fait quelque chose de tout à fait analogue à la réponse de @vilicvane, sauf que plutôt que throw new BreakSignal(), j’avais écrit return null et encapsulé tous les rappels .then suivants dans null_wrap comme

then(null_wrap(function (res) { /* do things */ }))

je pense que c'est une bonne réponse car cela évite beaucoup d'indentation et que l'OP a spécifiquement demandé une solution qui ne soit pas throw. Cela dit, je peux revenir en arrière et utiliser quelque chose qui ressemble davantage à ce que @vilicvane a fait car certaines promesses de bibliothèques pourraient renvoyer null pour indiquer autre chose que "casser la chaîne", ce qui pourrait prêter à confusion.

il s’agit plus d’un appel à plus de commentaires/réponses que d’une réponse "c’est vraiment la façon de le faire".

0
user2571090