0

I'm currently running through, and attempting to familiarize myself with promises, and I will cut through the introductory concepts, and get to the meat of the matter. Within NodeJS, using the library BlueBird. I don't want to defer the function calls, I'd also rather not pollute the code more than needed, even though this is introductory coding to get familiar with the premise, as when more advanced concepts are being attempted, I'll get lost on them. I attempted using 'asyn/await' with a try block, but God was that code messy, and didn't work... More or less guidelines:

  • I do not want to use Promise.settle, I still want to exit when a function rejects, I'd just like to stay within the Catch block if an error is raised.
  • I don't want to use async/await, yield, or generators in some weird way to stop the flow of the code as this is detrimental to the intended practice.
  • If the intended solution to handling this is wrong, please don't just say it's wrong, I understand people's time is valuable, but with limited knowledge of the workings of the language, as much documentation as can be thrown at me, or reasons why I'm wrong are more appreciated than not.
  • I've seen implementations of the concept in CoffeeScript, I don't see a viable reason to move to that syntax system at the moment, as most anything I may use the language for in the coming months would be bare Node back-end management.

Promises contain a catch mechanism built in, which works perfectly, if dealing with a standard single Promise.

// Try/Catch style Promises
funcTwo = function(activate) {
  return new Promise(function(resolve, reject) {
    var tmpFuncTwo;
    if (activate === true) {
      tmpFuncTwo = "I'm successful"
      resolve(tmpFuncTwo)
    } else if (activate === false) {
      tmpFuncTwo = "I'm a failure.";
      reject(tmpFuncTwo)
    } else {
      tmpFuncTwo = "Oh this is not good."
      throw new Error(tmpFuncTwo);
    }
  });
}

funcTwo(true)
  .then(val => {
    console.log("1: ", val)
    return funcTwo()
  })
  .catch(e => {
    console.log("2: Err ", e.message)
  })

The thing that causes me to be somewhat confused is attempting to uphold the same premise with Promise.all, the error is not handled, as the throw pushes directly to the main Controller. The exception that is thrown from this snippet never makes it to the the Catch block.

funcThree = function(val) {
  return new Promise(function(resolve, reject) {
    if (val > 0)
      resolve((val + 1) * 5)
    else if (val < 0)
      reject(val * 2)
    else
      throw new Error("No work for 0");
  })
}
// Output in Dev Console
/* 
  Extrending to the catch block handling, This will fail, the exception is thrown, and ignores the catch block. Terminating the program.
*/
Promise.all([funcThree(1), funcThree(0), funcThree(-3)])
  .then(function(arr) {
    for (var ind = 0; ind < arr.length; ind++) {
      console.log(arr)
    };
  }, function(arr) {
    console.log(arr)
  })
  .catch(function(e) {
    console.log("Error")
  })

I've attempted a simple work around, but I am somewhat new to the language, and am not sure if this is adhering to "Best Practices", as they have been drilled into my mind from Python guidelines.

// Promise all, exceptionHandling
funcThree = (val) => {
  return new Promise(function(resolve, reject) {
    if (val > 0)
      resolve((val + 1) * 5)
    else if (val < 0)
      reject(val * 2)
    else {
      var tmp = new Error("No work for 0");
      tmp.type = 'CustomError';
      reject(tmp);
    }
  })
}

/*
  This works, and doesn't cause any type of mixup
*/
Promise.all([funcThree(1), funcThree(0), funcThree(-3)])
  .then(
    arr => {
      for (var ind = 0; ind < arr.length; ind++) {
        console.log(arr)
      };
    }, rej => {
      if (rej.type == 'CustomError')
        throw rej;
      console.log(arr)
    })
  .catch(e => {
    console.log("Catching Internal ", e.message)
  })

This is using the Native Promise library, as well as bluebird

Is there a way to handle this more natively,

In regards to jfriend00's comment. What i mean to say is that I don't want the exception to be handled by anything but the try-catch block. When I attempt to use the same format as for a normal promise, things align perfectly, and my catch is acknowledged, and the error handled. Since Promise.all can only ever resolve/reject I don't think that there is a clean way of delegating the exception that is thrown from the second call to funcTwo in the second code snippet. More or less I'm not sure if what I've done as a workaround," reject, check if reject passed forward an error, and then throw it if it did", is a good solution or if it will cause some deep problem as code expands.

L.P.
  • 406
  • 5
  • 17
  • Could you elaborate on what kind of "messiness" you possibly get with async/await? As far as I can imagine, this always makes the code cleaner. – Wiktor Zychla Mar 11 '17 at 20:27
  • In short, the messiness is my code did not work, and was harder to follow than I wanted it to be. Long version: The lack of experience with it in itself caused me to write a severely over-nested program that, again due to my limitations with it, did not even push a resemblance of working properly. I've been dealing with JS for roughly 3 months over the past 2 years or so. I used the async-await module before, but at the time was told that it was also incorrectly used, so I entered callback hell, and now with promises I am attempting to alleviate that strain. – L.P. Mar 11 '17 at 20:36
  • If a promise throws an exception in the Promise executor callback (the fn you pass as in `new Promise(fn)`), then the exception will be caught and then Promise will be rejected so I can't really tell where you think "the error is not handled" in your code. You would have to be more specific about that for us to help with that. **Beyond that, I can't really tell exactly what question you're asking.** Please clarify if you want help. – jfriend00 Mar 11 '17 at 23:19
  • @jfriend00 I've updated the question with a more precise snippet of what I'd like to ask. – L.P. Mar 12 '17 at 00:03

1 Answers1

2

Since Promise.all can only ever resolve/reject I don't think that there is a clean way of delegating the exception that is thrown from the second call to funcTwo in the second code snippet.

In this code block of yours:

// Try/Catch style Promises
funcTwo = function(activate) {
  return new Promise(function(resolve, reject) {
    var tmpFuncTwo;
    if (activate === true) {
      tmpFuncTwo = "I'm successful"
      resolve(tmpFuncTwo)
    } else if (activate === false) {
      tmpFuncTwo = "I'm a failure.";
      reject(tmpFuncTwo)
    } else {
      tmpFuncTwo = "Oh this is not good."
      throw new Error(tmpFuncTwo);
    }
  });
}

There is no difference between a throw and a reject(). The throw is caught by the Promise constructor and turned into a reject(). Personally, I prefer to just use reject() in this case because I think function calls are a bit faster than exceptions.

I don't know if this is codified in a specification or not, but it is generally considered a good idea to reject with an Error object. So, I'd write your code like this:

function funcTwo(activate) {
  return new Promise(function(resolve, reject) {
    if (activate === true) {
      resolve("I'm successful");
    } else {
      let errMsg = activate === false ? "I'm a failure." : "Oh this is not good.";
      reject(new Error(errMsg));
    }
  });
}

Promises either resolve or reject. There is no third error condition that is different than a reject. An exception just becomes a rejection. So, if you have three states to return (like the above code), then you have to just decide how you're going to put the three states into resolve and reject.

Since this is just example code, there is no concrete recommendation here. If activate === false is not actually an error, just a different type of completion that shouldn't abort your other promises in Promise.all(), then you'd want that case to be a resolve(), not a reject(). But, there's no hard and fast rule what is what - it really just depends upon what behavior you want to be natural and simple for the callers so thus it varies from one situation to the next.

In addition, if you don't control the code that is in funcTwo here, then you can just put a .catch() handler on it before you pass it to Promise.all() and you can turn a specific rejection into a resolve if that's how you'd like the Promise.all() logic to work. Promises chain so you can modify their output before passing them on to higher level operations. It is analogous to using try/catch at a lower level to catch and exception and deal with it so higher level code doesn't have to see it (appropriate sometimes).

More or less I'm not sure if what I've done as a workaround," reject, check if reject passed forward an error, and then throw it if it did", is a good solution or if it will cause some deep problem as code expands.

In your Promise.all() code here:

/*
  This works, and doesn't cause any type of mixup
 */
Promise.all([funcThree(1), funcThree(0), funcThree(-3)]).then(arr => {
  for (var ind = 0; ind < arr.length; ind++) {
    console.log(arr[index]);
  }
}, rej => {
  if (rej.type == 'CustomError')
    throw rej;
  console.log(arr)
}).catch(e => {
  console.log("Catching Internal ", e.message)
})

Your first reject handler is not really helping you. It isn't doing anything that you can't just do in your one .catch() handler. Let me repeat this. There are only two outcomes from a promise reject and resolve. There is no third outcome for an exception. Exceptions that occur inside promise callbacks just become rejections. So, your above code can be changed to this:

/*
  This works, and doesn't cause any type of mixup
*/
Promise.all([funcThree(1), funcThree(0), funcThree(-3)]).then(arr => {
  for (var ind = 0; ind < arr.length; ind++) {
    console.log(arr)
  };
}).catch(e => {
  // there is no value for arr here, no result - only a reject reason
  console.log("Catching Internal ", e.message)
  if (rej.type === "CustomError") {
      // do something special for this type of error
  }
  // unless you rethrow here, this rejection will be considered handled
  // and any further chained `.then()` will see the promise as resolved
  // but since there is no return value, the promise will be resolved
  // with an undefined result
});

If you wanted to capture the reject in funcThree() early enough that it could allow the rest of the Promises in Promise.all() to still be tracked and still get their results, then you can either get a Promise.settle() implementation that will follow all promises to their conclusions regardless of how many reject or you can code your own special case:

function funcFour(val) {
    return funcThree(val).catch(err => {
       // catch and examine the error here
       if (err.type === "CustomError") {
           // allow things to continue on here for this specific error
           // and substitute null for the value.  The caller will have
           // to see null as a meaningful result value and separate from
           // a non-null result
           return null;
       } else {
           // Not an error we recognize, stop further processing
           // by letting this promise reject
           throw err;
       }
    });
}

Promise.all([funcFour(1), funcFour(0), funcFour(-3)]).then(arr => {
   // got results, some might be null
   console.log(arr);
}).catch(err => {
   // got some error that made it so we couldn't continue
   console.log(err);
});

I know this is all style opinion, but one of the benefits of good code using promises is that you stop having as deeply indented code and now I see people putting all sorts of extra indentation and lines that simply aren't needed and seem to remove some of the benefits of clean promise coding.

In this case, .then() and .catch() can go on the same line as the promise they follow. And, there's no need to start an inline function definition on a new line.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • With the scheme that you've proposed, for funcThree, where does the error get set? I see it's created and it's thrown, but in the case you've presented it doesn't let me validate it. Negating that, added the errMsg.type and fixed those things around. I looked into the implementation for Promises more, and do get the limitations. Lot's of random work-arounds for this, but all just gave me the feeling of more loss of power. The over-expression of the code is due more to the new-ness, I'm not comfortable enough for shortcuts, and the structure is to stay in line with C/C++ syntax, bad I know... – L.P. Mar 12 '17 at 01:01
  • Wasn't able to add the thank you for the response in the prior comment. I'' keep the Promise.settle approach in mind though, as for this particular exercise I didn't want to use it due to it's continuing on failure, but will need it sooner than not. – L.P. Mar 12 '17 at 01:03
  • @L.P. - The `funcThree()` I refer to is just any function of yours that returns a promise that will sometimes resolve and sometimes reject and some of those rejections you want to continue on with the rest of the processing. – jfriend00 Mar 12 '17 at 01:13
  • @L.P. - On the "stay in line with C/C++ syntax", don't even think of it. It's a bad idea. You should learn the best way to express syntax in each new language you learn and avoid trying to stay with something that is comfortable from another language. That will just lead to code that does not follow common conventions and even you will think it looks weird in a short while (I made the same mistake when going from C++ to JS). Learn the Javascript force Luke - it's not the same as your father's C++. – jfriend00 Mar 12 '17 at 01:16
  • @L.P. - There's no loss of power. You can return an infinite variety of results with resolve and an infinite variety of results with a reject. The point of the reject is to make things reject that should usually terminate other processing and abort down an error path. That will make your error handling clean, simple and will not have lots of special cases. If it's not really an error, just a different type of return, then resolve. Remember, you can reject or resolve with an object which gives you an infinite number of properties to communicate back result/status. – jfriend00 Mar 12 '17 at 01:18
  • @L.P. - The real power of promises shows up when you have dozens of coordinated asynchronous operations, some of which may be nested and you need to coordinate the timing and percolate up errors from multiple nested asynchronous callbacks. Trying to do that manually and reliably without promises is actually quite difficult - no matter how expert a coder you are. Doing it with promises is really quite simple once you learn promises. – jfriend00 Mar 12 '17 at 01:20
  • Lol. Words to live by. I stumbled into the same pit when learning Python, which was a hellish transition, but the strictness of the syntax differences made adhering to it faster. With regards to the resolve returns, I've been aiming to get comfortable with consistently returning an object from JS functions whenever possible., was guided away from that due to some work with a small team, and the adherance to err first callbacks. These points are more proofs of concepts to build up on the bigger projects. First time looking into promises spent about 2 hours trying to figure out what was wrong... – L.P. Mar 12 '17 at 01:21
  • ...To figure out that they don't work well with a three year old wait.for-es6 library. I tend to start from the ground up, even with thing's I'm somewhat familiar with. The power of Promises is not lost on me, I despise callbacks with damn near all my heart. With regards to moving into using modules, and promisification(bluebird) understanding how they work internally will help in figuring out what''s broken on the grand scale. – L.P. Mar 12 '17 at 01:24
  • @L.P. - In the interests of understanding how things work, you can see how one could generically promisify any async function that follows the node.js callback convention here: [Trying to understand how promisification works with BlueBird](http://stackoverflow.com/questions/38710136/how-to-support-api-calls-to-use-a-promise-or-callbacks-with-standard-promises-n/38710200#38710200). I wrote my own promisify function just to really understand and show how it works (I use Bluebird for my own code now). – jfriend00 Mar 12 '17 at 01:42
  • @L.P. - And, I strongly recommend the Bluebird promise library for these reasons: [Are there still reasons to use promise libraries like Q or BlueBird now that we have ES6 promises?](http://stackoverflow.com/questions/34960886/are-there-still-reasons-to-use-promise-libraries-like-q-or-bluebird-now-that-we). – jfriend00 Mar 12 '17 at 01:43
  • I'd actually read that second article due to the majority of my code being able to call the native Promise, and work properly, but upon looking into bluebird, saw that much more had been done to optimize things when possible, and it seems more future ready than the native implementation at the moment. – L.P. Mar 12 '17 at 01:55