For the last few months, I’ve been listening to Ryan Toronto and Sam Selikoff talk about React Suspense over on the Frontend First podcast. I don’t know much of anything about React Suspense, but it appears to work, at least in part, by throw()
ing Promise
objects in JavaScript. Obviously, the overwhelming majority of throw()
statements within a client-side application will use Error
instances. But, the fact that Suspense is throwing Promises got me wondering: can you throw()
anything in JavaScript?
Run this demo in my JavaScript Demos project on GitHub.
View this code in my JavaScript Demos project on GitHub.
To explore this, all I did was create an Array
of values, loop over those values, and try to throw()
them:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>
throw() Anything In JavaScript
</title>
</head>
<body>
<h1>
throw() Anything In JavaScript
</h1>
<script type="text/javascript">
// Let's create a collection of different types of JavaScript objects to see what
// happens when we throw() them around.
var values = [
null,
undefined,
true,
false,
1234,
new Date(),
"String Object",
[ "Array Object" ],
{ type: "Object Object" },
new Map().set( "foo", "bar" ),
new Set().add( "foo" ),
Promise.resolve( "Promise Object" ),
Promise.reject( "Rejection Object" ),
new Error( "Error Object" )
];
console.group( "Trying to throw() various objects in JavaScript." );
for ( var value of values ) {
try {
throw( value );
} catch ( error ) {
console.warn( "%cCatch:", "font-weight: bold", error );
}
}
console.groupEnd();
</script>
</body>
</html>
Ultimately, most things in JavaScript “extend” (ie, have in their prototype chain) the Object
constructor. As such, most of these tests are redundant. That said, I tried to create all the objects I could think of on the fly. And, when we run this JavaScript code, we get the following output:

As you can see, each value in my collection was successfully used in the throw()
statement and then consumed in the catch
block. So, not only can you throw Promise
objects in JavaScript, you can throw … anything!
Honestly, it would never occur to me to throw()
anything other than an Error
instance. However, if we shift our mindset over to Promises for a moment – and think about Promise
rejections, not errors – then the lines get a little more fuzzy. While I might never think to throw()
anything other than an Error
, I would certainly consider using non-Error
objects in my Promise
rejections.
In fact, when I was building my fetch()
-powered API client in JavaScript, part of the guarantee that it makes is that it will catch all internal errors and normalize them such that there is a consistent structure for all reasons that result in a Promise
rejection. An abbreviated version of this code looks like:
async makeRequest() {
try {
var fetchResponse = await fetch( ... );
var data = await this.unwrapResponseData( fetchResponse );
if ( ! fetchResponse.ok ) {
return( Promise.reject( this.normalizeError( data ) ) );
}
} catch ( error ) {
return( Promise.reject( this.normalizeTransportError( error ) ) );
}
}
As you can see here, the rejections in this API client are all passing through a “normalization” process that returns an Object
that is used as the rejection of the API call. And, for me, this feels completely natural and correct.
Now, to bring this back to the contemplation of throw()
: in this case, I’m returning an explicit rejection. However, one of the wonderful things about async
/await
Functions is that they will automatically catch errors and parle them into Promise
rejections. Which means, I can theoretically take the above code and re-write it using throw()
instead of Promise.reject()
:
async makeRequest() {
try {
var fetchResponse = await fetch( ... );
var data = await this.unwrapResponseData( fetchResponse );
if ( ! fetchResponse.ok ) {
throw( this.normalizeError( data ) );
}
} catch ( error ) {
throw( this.normalizeTransportError( error ) );
}
}
NOTE: I have not run this code – I’m just riffing off my mental model. So, forgive me if there are syntax errors or mistakes here.
These two blocks of code lead to the same exact behavior: they return a Promise
that is (in the case of an error) rejected with the normalized error Object
. And yet, the Promise.reject()
syntax feels so natural while the throw()
syntax feels so freaking strange.
It makes me question: does one of these syntax approaches express clearer intent?
And, if I’m being honest, the more I stare at this, the more the throw()
approach feels like it explains the workflow better. Or, at least, more consistently. If async
/await
is syntactic sugar over the use of Promises, it feels a bit odd that I’m pulling in Promise.reject()
as part of the control-flow – it feels like I’m mixing two different paradigms (even through they are technically the same exact thing).
On the other hand, throw()
feels like it’s living at the correct syntactic sugar level. If an async
function will naturally turn non-errors in Promise
fulfillments and errors into Promise
rejections, then using throw()
feels like the most consistent way to “return” the errors.
Something magical happened here: I started writing this post thinking it would just be a fun exploration of the throw()
statement. But, I ended up completely questioning my mental model. And, when all was said and done, I think I’ve actually started to evolve my thinking. Yesterday, the idea of throwing an “Object” in JavaScript felt gross. Today, I think it kind of makes sense.