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
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: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.
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 thecatch
block of an async try/catch) implicitly assumes the caught value is an error and will do things like try to destructure amessage
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.Promise.resolve
(and its related behavior innew Promise
,then
return values, andasync
) 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 ofthrow
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 thereject
case.Furthermore, it is unclear what to do when
throw
ing 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 theresolve
path in further chaining, thereby makingreject(promiseValue)
equivalent toresolve(promiseValue)
? Error cases analogous tothrow
will almost certainly have areason
value synchronously, so there’s no reason tothrow
a Promise, and in handlers if you need asynchronous error details you canreturn
a Promise that you know will eventuallyreject
.