Search code examples
javascriptpromisejquery-deferred

Is there a good way of short circuiting Javascript promises?


I'm a bit of a novice with promises/Deferreds. Is there a good pattern to handle the case where one might want to short circuit a chain of promises, for both success and error cases? In the error situation, I know you can chain a .then(null, function(error) {}) to the end and catch an error from any of the previous thens, but what if you want to handle an error in a more custom way and terminate? Would you specify a 'type' of error in an earlier error handler and return it via a new promise, to be handled or skipped in the final error handler? And what about a success case, where you want to terminate earlier in the chain (only conditionally firing off any later then's)?


Solution

  • Typically, the promise chain starts with a call to some asynchronous function as such:

    var promise = callAsync();
    

    If you are chaining a second async call, you probably do something like this:

    var promise = callAsync()
    .then(function(){
        return callOtherAsync();
    })
    .then(function(){
        return callSuccessAsync();
    }, function(){
        return callFailAsync();
    });
    

    As a result of chaining, promise now contains the final promise which completes when callFinalAsync()'s promise completes. There is no way to short circuit the final promise when using this pattern - you can return a failed promise along the way (for instance, rather than returning the result of callOtherAsync) but that requires the failed promise to progress through the chain (thus causing callFailAsync to be called). You can always fulfill or reject the promise from within the callbacks as such

    var promise = callAsync()
    .then(function(){
        if(fail){
            promise.reject();
            //no way to halt progression 
        }else{
            return callOtherAsync();
        }
    })
    .then(function(){
        return callSuccessAsync();
    }, function(){
        return callFailAsync();
    });
    

    however, this will not prevent calls to callFailAsync(). Some Promise/A implementations expose a stop method for just this purpose. With stop, you could do this:

    var promise = callAsync();
    .then(function(){
        if(fail){
            this.stop(); 
            promise.reject();
        }else{
            return callOtherAsync();
        }
    })
    .then(function(){
        return callSuccessAsync();
    }, function(){
        return callFailAsync();
    });
    

    Which depends on having access to the intermediate promise with this. Some Promise implementations forbid that (forcing this to be window/null/etc), but you can deal with that with a closure.

    TL;DR: Promise/A spec doesn't provide a chain short circuit function, but it's not hard to add one.