how to break promise chain

I a promise in such fashion,

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 returns a promise, and yes checkIf cannot be modified.

How do I break out of the chain at the first match? (any way other than explicitly throwing error?)

Answers:

Answer

You can use return { then: function() {} };

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

The return statement returns a "then-able", only that the then method does nothing. When returned from a function in then(), the then() will try to get the result from the thenable. The then-able's "then" takes a callback but that will never be called in this case. So the "then()" returns, and the callback for the rest of the chain does not happen.

Answer

I would just use coroutines/spawns, this leads to much simpler code:

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
}

If you don't have generators then there's always traceur or 6to5.

Answer

i like a lot of the answers posted so far that mitigate what the q readme calls the "pyramid of doom". for the sake of discussion, i'll add the pattern that i plunked out before searching around to see what other people are doing. i wrote a function like

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

and i did something totally analogous to @vilicvane's answer, except rather than throw new BreakSignal(), i'd written return null, and wrapped all subsequent .then callbacks in null_wrap like

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

i think this is a good answer b/c it avoids lots of indentation and b/c the OP specifically asked for a solution that doesn't throw. that said, i may go back and use something more like what @vilicvane did b/c some library's promises might return null to indicate something other than "break the chain", and that could be confusing.

this is more a call for more comments/answers than a "this is definitely the way to do it" answer.

Answer

Probably coming late the party here, but I recently posted an answer using generators and the co library that would answer this question (see solution 2):

The code would be something like:

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

You would pretty much write synchronous code that would be in reality asynchronous !

Hope it helps!

Answer

Try to use libs like thisone:

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

I think you don't want a chain here. In a synchronous fashion, you'd have written

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

and this is how it should be translated to 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();
            });
        });
    });
}

There is no if else-flattening in promises.

Answer

Any way other than explicitly throwing error?

You may need to throw something, but it does not have to be an error.

Most promise implementations have method catch accepting the first argument as error type (but not all, and not ES6 promise), it would be helpful under this situation:

function BreakSignal() { }

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

I add the ability to break in the recent implementation of my own promise library. And if you were using ThenFail (as you would probably not), you can write something like this:

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

You could create a firstSucceeding function that would either return the value of the first succeeded operation or throw a NonSucceedingError.

I've used ES6 promises, but you can adapt the algorithm to support the promise interface of your choice.

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

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us Javascript

©2020 All rights reserved.