Why are exceptions used for rejecting promises in JS?
Actually, the API in that spec emerged as a consensus amongst various implementations. However, some points that might have led to this are:Why wasn't the api designed in such a fashion where for the then function, it is passed a resolve and reject function like the original promise constructor?
then
is a rather functional method. It's callback only should receive one data argument, the result value of the promise.- Passing additional
resolve
/reject
functions to the callback does not work well with multiple arguments or even variadic functions. then
is usually used as a plain mapping function. You simplyreturn
the new value, noresolve
is needed.- When you really want to do something asynchronous in your callback where you could make use of
resolve
/reject
, you better should use a promise anyway - which you simply can return then.
resolve
/reject
arguments, but it was tedious to use - and I seldom needed them because of #4. Using them was error-prone, you could easily forget something (like handling errors, or progress events) - just like the people who are manually constructing and returning deferreds that are resolved from promise callbacks, instead of calling then
.They're not really meant to be used for control flow (like branching, loops etc) but for exception handling: rejections are exceptional. Most Promise developers wanted to implement them as an alternative for synchronous (blocking) code - where IO was always throwing exceptions, so they adapted this. Rejections are still explained as the asynchronous equivalent toExceptions are heavy so it seems odd they are using them as a choice for flow control.
try … catch
, though their monadic nature could be utilized in mightier ways and higher-level applications.There's not much difference betweenCreating a whole new promise object and returning it, just to reject it, adds to code bloat IMO.
return new RejectedPromise(…)
, return reject(…)
and throw Error(…)
.Most Promise developers seem to see this as an advantage actually - (unexpected) exceptions even in asynchronous code will be caught automatically, so they can be handled instead of blowing up the program (unnoticed). See also exception handling, thrown errors, within promises and acceptable promise pattern for 'LOUD' errors?.Debugging becomes harder too in case of an exception is thrown (such as syntax errors, or if function is being called on a undefined object, etc.)
JavaScript Promises - reject vs. throw
There is no advantage of using one vs the other, but, there is a specific case where throw
won't work. However, those cases can be fixed.
Any time you are inside of a promise callback, you can use throw
. However, if you're in any other asynchronous callback, you must use reject
.
For example, this won't trigger the catch:
new Promise(function() {
setTimeout(function() {
throw 'or nah';
// return Promise.reject('or nah'); also won't work
}, 1000);
}).catch(function(e) {
console.log(e); // doesn't happen
});
Avoid uncaught exception when a promise gets rejected due to timeout?
Instead of racing a promise with a short-lived promise(rejectAfterDelay), we can wrap the promise in a short-lived promise:
async function delayedPromise(delay) {
return new Promise((res) => setTimeout(res, delay, 'success'));
}
// wrap the promise instead of racing it
function rejectAfterDelay(promise, ms) {
return new Promise((resolve, reject) => {
setTimeout(reject, ms, 'timeout');
// forward the reasons to the wrapper
promise.then(reason => resolve(reason))
.catch(err => reject(err));
});
}
async function main() {
// Create array of promises.
promArr = [];
promArr.push(delayedPromise(100));
promArr.push(delayedPromise(200));
promArr.push(delayedPromise(300));
promArr.push(delayedPromise(400));
promArr.push(delayedPromise(500));
// Wait for all promises to either get fulfilled or get rejected after 200 ms.
const msMaxTime = 200;
const result = await Promise.allSettled(
promArr.map(promise => {
//return Promise.race([promise, rejectAfterDelay(msMaxTime)]);
return rejectAfterDelay(promise, msMaxTime);
})
);
console.log(result.map(r => r.value ? r.value : r.reason));
}
main()
Difference of exception and unfulfilled promise?
An unfulfilled Promise is simply one that hasn't been fulfilled, which is quite possible even if it doesn't reject. For example:
const prom = new Promise((resolve) => {
// code here that never calls `resolve`
});
That is a Promise that will never be fulfilled - but it won't reject either.An "unfulfilled" Promise may not necessarily hang forever like the above - it's simply a Promise that hasn't resolved yet.
a throw is always an exception?To an extent, yes, though it behaves somewhat differently when inside a Promise - it won't cause an
error
event. Instead, it'll try to find a .catch
handler in the Promise chain it's currently in, and if it doesn't find one, an unhandledrejection
event will be fired.And why are exceptions only for synchronous code?That's just how the language was designed.
Why doesn't the try catch block catch the promise exception?
I can see two possible aspects to your question:
The error being thrown in
test
is in the synchronous (not asynchronous) part oftest
. Why is it a promise rejection rather than a synchronous exception?The promise from
test()
is being rejected, why doesn'tcatch
catch that rejection?
#1 - Why is it a rejection?
Because even if anasync
function throws during the synchronous portion of its work, that just rejects the promise it returns, it doesn't raise a synchronous error. It's just a design decision made during the design of async
functions, and a smart one — if it were a synchronous error when throwing in the synchronous part of the function but a promise rejection after that, it would be chaotic and hard to understand. So it's simple: throwing in an async
function always rejects its promise.(This is consistent with how the promise constructor treats the executor callback you pass into it. When you do new Promise((resolve, reject) => /*...*/})
, the promise constructor calls your function synchronously, but if you throw during the call, it uses what you throw to reject the promise rather than allowing it to continue as a synchronous exception.)
#2 - Why isn't rejection caught by the catch
block?
Because you're not using await
. Promise rejections are only exceptions when you are await
ing the promise. If you use the promise methods then
/catch
/finally
to attach handlers, rejection is handled by calling the rejection handler, not by the exception mechanism.So either use promise methods to attach fulfillment and rejection handlers:
test()
.then(result => {
// ...use `result` here...
})
.catch(error => {
// ...handle/report error here...
});
Or use await
in an async
function (or at the top level of a module if you have top-level await
in your enviroment):// In an `async` function (or the top level of a module in cutting-edge environments)
try {
const result = await test();
// ...use `result` here...
}
catch(err){
// ...handle/report error here...
}
Why does Promise.all() throw an exception even if I .catch() it?
As pointed out by Bergi in the comments...
If you execute your first bit of code without the catch statement (or actually print the errors you catch) you'll see what is happening.
Promise.all(new Promise((res, rej) => rej('Failure!')))
Returns:Promise {<rejected>: TypeError}
Uncaught (in promise) Failure!
Uncaught (in promise) TypeError: object is not iterable (cannot read property Symbol(Symbol.iterator))
at Function.all (<anonymous>)
at <anonymous>:1:9
Notice the first error is the error we throw by rejecting the promise.The second error comes from not using Promise.all()
correctly, which is the error you were catching.
The error we throw by rejecting the promise is never caught because of the incorrect usage of the Promise.all()
method.
Now, let's test out the code with our promise inside an array, which is the correct usage of
Promise.all()
, as pointed out by Barmar in the comments.Promise.all([new Promise((res, rej) => rej('Failure!'))])
.catch(() => console.log("It's all okay."))
Returns:Promise {<fulfilled>}
It´s all okay.
So, we are successfully catching the rejected promise error.Also worth noting, the final promise returned is the promise that the catch()
method returns after execution. Which is fulfilled since we successfully executed the catch statement, although the promise that Promise.all()
actually returns is rejected.
Related Topics
How to Add a "Readonly" Attribute to an <Input>
JavaScript Getelementbyname Doesn't Work
JavaScript When to Use Prototypes
How to Sign into Google with Selenium Automation Because of "This Browser or App May Not Be Secure."
How to Use Local Storage in Angular
How Is a JavaScript Hash Map Implemented
How to Set the Style -Webkit-Transform Dynamically Using JavaScript
Decompress Gzip and Zlib String in JavaScript
How to Validate Google Recaptcha V2 Using JavaScript/Jquery
Unchecked Runtime.Lasterror While Running Tabs.Executescript
Getting a Union of Two Arrays in JavaScript
How Does Appcelerator Titanium Mobile Work
JavaScript Toisostring() Ignores Timezone Offset
Adding New Data to Firebase Users