skip to Main Content

So I was reading the article on MDN about Promises and got stuck here:

Unlike Promise.resolve(), Promise.reject() always wraps reason in a new Promise object, even when reason is already a Promise.

I’m just trying to understand what difference it makes in coding. What would happen if Reject doesn’t wrap the reason with another Promise just as resolve does and how exactly this simplifies the usage.

Appreciate some hypothetical examples.

2

Answers


  1. We can’t speak to the design philosophy except for those who designed the A+ spec. But as to the actual examples…

    Promises automatically flatten on the happy path: Promise.resolve(Promise.resolve(5)); is just a Promise of 5, not a Promise of a Promise of 5.

    But Promise.reject does not automatically flatten, Promise.reject(Promise.resolve(5)) actually is a (rejected) Promise of a Promise of 5:

    Promise.reject(Promise.resolve(5))
      .catch(p => console.log(Object.prototype.toString.call(p)))
    

    will log [object Promise].

    Side Note: the decision for Promises to have that auto-flattening behavior on the happy path was controversial at the time of the proposal back in 2013, many people felt and still feel that Promises should not do that for various reasons, you can read the linked Github discussion for details. If you want to see what a non-automatically-flattening Futures API looks like, check this out.

    What would happen if Reject doesn’t wrap the reason with another Promise just as resolve does

    the Promise would autoflatten and you’d just get the rejection value of 5 instead of Promise of 5.

    This may be a neat piece of trivia, but there is a strong convention in the JS community to not reject Promises with non-Errors (like other Promises). I seem to recall some controversy over React.js rejecting Promises with non-Errors but I’m having a little trouble tracking down the reference.

    At any rate, a lot of .catch code (or code in the catch block of an async try/catch) implicitly assumes the caught value is an error and will do things like try to destructure a message property which will throw at runtime (which is why Typescript will not let you do that without a cast or runtime check) so please consider very carefully before you decide you need to reject with something that isn’t an instance of Error.

    Login or Signup to reply.
  2. Promise.resolve (and its related behavior in new Promise, then return values, and async) ensures that Promises don’t become nested: you’re indicating that the value to return or resolve isn’t ready yet and might actually wind up as a rejected promise. Promise.reject can be simpler, as it is an asynchronous analog of throw and can therefore treat its results opaquely.

    This matches the original proposal in Promises/A+, where both 2.2.7 and 2.3.3.3 specify the that the promise resolution process runs on the resolve case but not the reject case.

    Furthermore, it is unclear what to do when throwing a Promise that resolves: Is the resolved value the reason that should be passed along? Or does successful return of the function mean that the path should switch back to the resolve path in further chaining, thereby making reject(promiseValue) equivalent to resolve(promiseValue)? Error cases analogous to throw will almost certainly have a reason value synchronously, so there’s no reason to throw a Promise, and in handlers if you need asynchronous error details you can return a Promise that you know will eventually reject.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search