Как сломать цепочку обещаний

Я обещаю таким образом,

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 возвращает обещание, и да checkIf не может быть изменено.

Как я вырваться из цепочки в первом матче? (каким-либо образом, кроме явно бросая ошибку?)

Ответ 1

Я думаю, что вы не хотите здесь цепочки. Синхронно, вы бы написали

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

и вот как это должно быть переведено на promises:

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();
            });
        });
    });
}

В promises нет if else -flattening.

Ответ 2

В любом случае, кроме явного отклонения ошибки?

Возможно, вам нужно что-то бросить, но это не должно быть ошибкой.

Большинство реализаций обещаний имеют метод catch, принимающий первый аргумент как тип ошибки (но не все, а не обещание ES6), было бы полезно в этой ситуации:

function BreakSignal() { }

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

Я добавляю способность ломаться в недавней реализации моей собственной библиотеки обещаний. И если вы использовали ThenFail (как вы, вероятно, не знаете), вы можете написать примерно так:

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

Ответ 3

Я бы просто использовал сопрограммы/нересты, это приводит к значительному упрощению кода:

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
}

Если у вас нет генераторов, тогда всегда отслеживается или 6to5.

Ответ 4

Вы можете использовать return { then: function() {} };

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

Оператор return возвращает "then-able", только тогда метод then ничего не делает. При возврате из функции в then(), then() попытается получить результат от thenable. Тогда-то "затем" принимает обратный вызов, но в этом случае он никогда не будет вызван. Таким образом, "then()" возвращается, а обратный вызов для остальной части цепочки не выполняется.

Ответ 5

Вы можете создать функцию firstSucceeding, которая либо вернет значение первой успешной операции, либо выбросит NonSucceedingError.

Я использовал ES6 promises, но вы можете адаптировать алгоритм для поддержки интерфейса обещаний по вашему выбору.

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); });

Ответ 6

Мне нравится много ответов, опубликованных до сих пор, которые смягчают то, что q readme вызывает "пирамиду обречения". для обсуждения, я добавлю шаблон, который я выложил, прежде чем искать, чтобы узнать, что делают другие люди. я написал такую ​​функцию, как

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);
  };
};

и я сделал что-то совершенно аналогичное ответу @vilicvane, кроме, чем throw new BreakSignal(), я написал return null и завернул все последующие обратные вызовы .then в null_wrap, например

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

Я думаю, что это хороший ответ b/c, он избегает множества отступов и b/c OP специально запрашивает решение, которое не throw. что я могу вернуться и использовать что-то более похожее на то, что @vilicvane сделал b/c, некоторая библиотека promises могла бы вернуть null, чтобы указать что-то другое, кроме "разбить цепочку", и это может сбить с толку.

это скорее призыв к большему количеству комментариев/ответов, чем ответ "это определенно способ сделать это".

Ответ 7

Вероятно, поздняя вечеринка здесь, но я недавно опубликовал ответ с использованием генераторов и co библиотека, которая ответила бы на этот вопрос ( см. решение 2):

Код будет выглядеть примерно так:

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);

Вы в значительной степени напишите синхронный код, который был бы в действительности асинхронным!

Надеюсь, что это поможет!

Ответ 8

Слишком поздно, но может помочь другим, как я

doSomeThingAsync()
  .then(x => { 
     if(condition){
      throw x
     }
     else{
       return x
     }
  })
   .then()
   .then()
   .catch(x => {
     if(! (x instanceof Error )){
       return x
     } 
     else{
       throw x
     }
   })
    .catch(errorHandler)

Ответ 9

Попробуй использовать libs вот так:

https://www.npmjs.com/package/promise-chain-break

    db.getData()
.then(pb((data) => {
    if (!data.someCheck()) {
        tellSomeone();

        // All other '.then' calls will be skiped
        return pb.BREAK;
    }
}))
.then(pb(() => {
}))
.then(pb(() => {
}))
.catch((error) => {
    console.error(error);
});