8

So i have been playing with promises for the last few days, and just trying to convert some project, to use promises, but more than a few times i have encuntered this issue.

While reading articles and tutorials everything looks smooth and clean:

getDataFromDB()
.then(makeCalculatons)
.then(getDataFromDB)
.then(serveToClient)

But in reality, its not like that.
Programs have a lot of "if conditions" that changes the whole flow:

getDataFromCache(data).then(function(result){
    if(result){
        return result;
    }else{
        return getDataFromDB();
    }
}).then(function(result){
    if(result){
        serveToClient() //this does not return a promise, so undefined returned...
    }else{
        return getDataFromWebService(); //this does return a promise, 
    }
}).then(function(result){
    //i dont want to reach here if i already serveToClient()...
    //so i basically have to check "if(result)" for all next thens
    if(result){
       //do more stuff
    }
}).then(...

I have 2 major issues:

  1. I find myself adding alot of if conditions on the then callbacks.
  2. I am still getting into the next then callbacks, even if i already finished (serveToClient)


Am i following the correct pattern?

yosiweinreb
  • 475
  • 4
  • 12

2 Answers2

6

You can't avoid the if statements since that is required for your logic flow. You will have to branch your flow of control if you don't want to continue the promise chain in one part of the if. So if in some part of your second .then() handler, you don't want to go on to the third .then() handler, then you need to branch the logic like this and put subsequent .then() handlers inside the 2nd .then() handler in their own branch of the logic.

You can't just continue the top level branch because the only way to abort future .then() logic in the main chain is to either reject the promise (which you probably don't want to do) or add another if check in every .then() handler to decide whether it should be skipped or not (yuck).

So instead, you can branch the logic like this:

getDataFromCache().then(function(result){
    if(!result) {
        return getDataFromDB()
    } else {
        return result;
    }
}).then(function(result){
    // branch promise chain here into two separate branches
    if(result){
        // do not continue the promise chain here
        // call a synchronous operation
        serveToClient();
    } else {
        // continue promise chain here
        return getDataFromWebService().then(function(result) {
            if(result){
               //do more stuff
            }
        }).then(...);    // you can continue the promise chain here
    }
}).catch(function(err) {
    // process any errors here
});

You may find these other answers useful:

Understanding javascript promises; stacks and chaining

Is there a difference between promise.then.then vs promise.then; promise.then


FYI, you can reorganize the above code to be a bit more concise like this:

getDataFromCache().then(function(result) {
    if (result)
        serveToClient();
    } else {
        return getDataFromWebService().then(function(result) {
            if(result){
               //do more stuff
            }
        }).then(...);    // you can continue the promise chain here
    }
}).catch(function(err) {
    // process any errors here
});
Community
  • 1
  • 1
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Added other reference answers. – jfriend00 Nov 03 '16 at 22:35
  • Well my issue with that way is the subsequent `.then()`. isn't one of the core reasons for Promise is to avoid the callback pyramid? – yosiweinreb Nov 03 '16 at 22:39
  • 2
    @yosiweinreb - You need only as many levels of indent as your logic requires. For strict sequential execution, you need only one long chain and no extra nesting. Everytime you want to branch your chain with an `if` statement you have to add one level of nesting. Think about it, it's not really any different than synchronous programming. If you add an `if` in synchronous programming that doesn't want to continue the rest of the logic, then you add a level of nesting there too. When you start figuring out how to handle error conditions, promises will be massively better than callbacks. – jfriend00 Nov 03 '16 at 22:45
3

The other answer explains branching, but you also asked for "smooth and clean".

You can use ES6 arrow functions:

getDataFromCache()
  .then(result => result || getDataFromDB())
  .then(result => result ? serveToClient() : getDataFromWebService()
    .then(result => { /* Won't reach here if serveToClient */ }))
  .then(() => { /* can continue promise chain here */ })
  .catch(e => console.log(e));

Notice the indented .then is on getDataFromWebService(), and see the double )) at the tail. This nicely mirrors synchronous branching.

You can use ES8 async/await (now available in Chrome Canary and Firefox Nightly!):

try {
  if (await getDataFromCache() || await getDataFromDB()) {
    serveToClient();
  } else {
    let result = await getDataFromWebService();
    /* Won't reach here if serveToClient */
  }
  /* can continue here */
} catch (e) {
  // process any errors here
}

The latter gives you full control as if things were synchronous, provided it's inside async functions.

jib
  • 40,579
  • 17
  • 100
  • 158
  • 1
    Note that it's feasible to use `async/await` in production today if you don't mind using a transpiler - see http://babeljs.io/. – Matt Browne Nov 04 '16 at 00:37