Best way to use if/else in a promise chain

hello all-

i had a look at this SO reply for the best way to include an if/else in a promise chain. the accepted solution looks like this:

new Promise(resolve => resolve({success:true}))
.then(value => {
    console.log('second block:', value);
    if (value.success) {
        //skip the rest of this chain and return the value to caller
        return value;
    }
    //do something else and continue
    return somethingElse().then(value => {
        console.log('3rd block:', value);
        return value;
    });
}).then(value => {
    //The caller's chain would continue here whether 3rd block is skipped or not
    console.log('final block:', value);
    return value;
});

i played around with it, this does seem to work just fine. :blush:

notice the syntax using the two return statements (interesting)

*return* somethingElse().then(value => {
    console.log('3rd block:', value);
    *return* value;
});

would this be the preferred way to embed an if/else into a js promise chain?

i found another older SO solution here, but this solution seems like it creates too many unnecessary function statements.

(why are promises so difficult for me to master? please dont answer thatā€¦)

I donā€™t understand why you would use a success in the resolve return? Thatā€™s why there is a resolve and reject. For me resolve is only called if success is true otherwise reject is called.

2 Likes
        return new Promise((resolve, reject) => {
            xhr.open(options.method || 'get', url, true);

            xhr.upload.addEventListener('progress', options.onProgress || (() => {
            }));

            xhr.addEventListener('load', () => {
                if (xhr.status >= 200 && xhr.status < 300) {
                    resolve(xhr.responseText);
                } else {
                    reject(new Error(xhr.statusText));
                }
            });

            xhr.addEventListener('error', () => reject(new Error(xhr.statusText)));
            xhr.addEventListener('abort', () => reject(new Error('Request aborted')));

            xhr.send(options.body);
        });

to me itā€™s pretty straightforward.

1 Like

If you look at the original question from SO

I know I can throw an error or reject it on the second block but I have to handle error case which it is not an error case. So how can I achieve this in promise chain? I need a solution without bring any other 3rd party library.

All a bit confusing.

Iā€™m not a fan of nested thenables, seems that is just another version of callback hell to me. Not sure if I have the wrong end of the stick here, but another option might be to use async await.

const somethingElse = async (value) => {
  console.log('second block:', value);
  
  return value
}

const foo = async () => {
  // switching to true will avoid calling somethingElse
  const value = await Promise.resolve({ success: false })

  return (!value.success)
    ? await somethingElse(value)
    : value
}

foo()
  .then((value) => console.log('final block:', value))
1 Like

quite.

To phrase it in resolve/reject terms, it would be more:

new Promise((resolve,reject) => { doSomething(); })
  .then(somethingAccepted,somethingRejected)
  .then(somethingFinal);

I have found by experience that

if(condition) {
    // do things
    return;
    }
// do other stuff
return;

will do other stuff even when the condition is true
Better is

if(condition) {
    // do things
    return;
    } else {
   // do other stuff
   return;
   }

Then only the one block will run.

1 Like

Unless I am mistaken, if the condition is true it will not do other stuff. It will exit out of the function, in this case returning undefined.

The only way I can see it unexpectedly carrying on to do other stuff is if there is an issue with the condition not being explicit enough. Say for instance when dealing with numbers not taking into account that 0 evaluates to false. I have seen that issue before.

1 Like

i should have mentioned that i am calling promises that are already formatted.

a Knex promise is called to see if a parent table contains a row, and if so the id is returned. if not, the parent row is created and the new id is returned. then the child table is checked, same as parent row.

from the forum comments, i have pieced this little example together to simulate what i need to do.

/* function to return a true or false */
const trueFalse = (delay) => {
   return new Promise((resolve, reject) => {
     setTimeout(() => {
       resolve(  Math.random() < 0.5   );  /* random true or false */
     }, delay);
   });
};

and this block of code will call it once, but for every false, it will try again.

trueFalse(1000)
  .then( flag => {
    console.log('first then:' + flag); 
    if  (flag) { 
        return flag;
    } else {
        console.log('first then trying again');
        return trueFalse(2000).then( flag => return flag)
    }
  })
  .then( flag => {
    console.log('second then: ' + flag); 
    if  (flag) { 
        return flag;
    } else {
        console.log('second then trying again');
        return trueFalse(3000).then( flag => return flag )
    }
  })
  .then( flag => {
    console.log('third then: ' + flag); 
    if  (flag) { 
        return flag;
    } else {
        console.log('third then trying again');
        return trueFalse(3000).then( flag => return flag )
    }
  })
      /* notice use of finally rather than `then` */
  .finally( final => console.log('final: ' + final) )  
  ;

opinions?
if you hit four false calls, you wait 10 seconds and should avoid Las Vegas and Atlantic City.

I mean, not far from what I came up with, but less returns all over the shopā€¦

const trueFalse = (delay) => {
   return new Promise((resolve, reject) => {
     setTimeout(() => {
       let result = Math.random();
       ( result < 0.5) ? resolve(result) : reject(result);  /* this line changed from yours. */
     }, delay);
   });
};

let final; //For holding result.

trueFalse(1000)
.then((result) => {console.log("Ejected First: " + result); final = result; } , (result) =>
     trueFalse(2000)
     .then((result) => {console.log("Ejected Second: " + result); final = result; } , (result) =>
        trueFalse(3000)
        .then((result) => {console.log("Ejected Third: " + result); final = result; } ,(result) => {console.log("Ejected Final: " + result); final = result; })
     )
);

but if you have no control over the underlying poorly formatted promises, thenā€¦ ya, youā€™ll have to go with something more like yours.

nothing wrong with the promises i am using, we need a different promise depending on the outcome of the previous result. i only [re]used the trueFalse example just as a simple example.

also, it was sort of fun, in a strange sort of way. :innocent:

the pseudocode looks like this:

if  parent row exists
then 
    return parent row id
else
    create new parent and return the new parent row id
end-if

if child row exists (using parent row id from previous step)
    return child row id
else
    create child row and return child row id
end if

so here we have at least four different promises:

  1. get parent id
  2. create parent & return new id
  3. get child id
  4. create child & return new id

and then will come the grandchildren rows.

Well if the promise returns resolve when it encounters a problem and fails, thenā€¦yes, thereā€™s something wrong with it :smiley:

Youā€™d never get to the child row.

if (x) { return true; } else { return false; }; console.log("You'll never get here, because all code paths have already returned.");

Why not

let parentRow = await getParentRow()
If(!parentRow)
    parentRow = await createParentRow(); 

And same for childRow. Or put it in a loop,

A try/catch around all of it and if any promise fails handle it in the catch block.

1 Like

Something like this? I think this works.

const getStatus = (function() {
  let delay = 0
  
  return function (delay) {
    delay += 1000
    
    return new Promise((resolve, reject) => {
      setTimeout( () => {
        resolve( Math.random() < 0.5 )
      }, delay)
    })
  }
})()

const attempt = async (messages) => {
  
  for (let message of messages) {
    const status = await getStatus()
    console.log(message, status)
    
    if (status) {
      return `success on ${message}`
    }
  }
  
  return 'all attempts failed'
}

attempt(['first attempt', 'second attempt', 'third attempt'])
  .then(console.log)
1 Like

sorry should have NOT used return in pseudocode but maybe used determine instead:

I now have something that appears to function as desired:

getParent(parentParms)
        .then( (parentObj) => {
                if  ( parentObj ) {
                        return parentObj.data
                } else {
                        return createParent(    parentParms )
                                .then( parentObj => {
                                        if  ( parentObj )       {
                                                return parentObj.parentId               ;
                                        } else {
                                                console.warn('missing: ' + JSON.stringify(parentObj));
                                        }
                                })
                                .catch (err => console.error(err) )
                                ;
                }
        })
        .catch ( err => console.error(err) )
        .then( parentId => {

                PARENT_ID = parentId;

                let childParms = ...  // build child parameters with parentId

                return getChild(childParms)
                        .then( (childObj) => {
                                if  ( childObj.total && childObj.data.length ) {
                                        return childObj.Id
                                } else {
                                        childParms.newElement = 'new Element value'; // new element specifically for child creation
                                        return createChild(childParms)
                                                .then( childObj => {
                                                        if  ( 'childId' in childObj )   {
                                                                return childObj.childId         ;
                                                        } else {
                                                                console.warn('missing:  ' + JSON.stringify(childObj));
                                                        }
                                                })
                                                .catch (err => console.error(err) )
                                                ;
                                }
                        })
                        .catch ( err => console.error(err) )
                        .then( childId => {
                                CHILD_ID = childId;
                        })
        })
        ;

As you can see there are four functions: getParent, createParent, getChild, and createChild.
these four functions are actually FeathersJs functions. each function looks almost identical:

async function getParent(query) {
        return await feathersApp.service('parent-table').find(query) ;
}

its tempting to just have one FeathersJs function and pass in the ā€œservice-nameā€ (in this example, ā€˜parent-tableā€™) as an additional parm.

side-note: the more i use FeathersJs the more i love it. FeathersJs generate statement takes mere seconds to create a new table service.