`rethrow` operator

The idea is simple: a rethrow key word to use inside catch blocks. This would cause errors that are re-thrown to be thrown as if from their original location, so as to not disrupt the error stack.

For example:

try {
  // ...
} catch (e) {
  if (!(e instanceof SomeSpecificError)) rethrow e // make it behave as it would have normally

  // ... handle special error ...
}

The idea is that the error would behave exactly as if it was thrown from its original location before try catch would have intercepted it, as if there never were a try catch.

Currently, when we re-throw errors using throw, the stack trace that dev tools shows will not be exactly as where the error originated, but will include the new throw location (can be confusing).

That’s not correct; stack traces are fixed when an error is created; re throwing it has no effect. If it does, file a bug on that browser.

3 Likes

@ljharb the developer tools can only pause where an error is thrown (throw expr), not where the Error instance has been created (new Error)

@JackWorks the developer tools can however pause both in the location where it is thrown the first time (if break on caught exceptions is enabled) and in the location where it is re-thrown (if break on exceptions is enabled).

Dev tools can choose whatever behavior they like; regardless, a stack trace (which is what the OP described) is never generated from throwing - only from creating a new error object.

I suppose it is the devtools behavior that is not ideal. Maybe the spec can address that; that's what the rethrow idea was for. I do see that if I inspect the error.stack I see the original stack. But devtools shows me other stuff based on where an error is re-thrown.

Essentially what I was hoping for is a way to cause the try-catch to behave as if it was never there.

Who's dev-tools are we talking about here?

I tried using the following code sample:

function fn1() {
  try {
    fn2();
  } catch (err) {
    throw err;
  }
}
function fn2() {
  throw new Error('Whoops!');
}
fn1();

In Chrome's REPL, I get the correct stack trace

image

Firefox's REPL works as well:
image

Here's Node's

Uncaught Error: Whoops!
    at fn2 (REPL10:2:9)
    at fn1 (REPL7:3:5)

If I'm using breakpoints in Chrome, I still get the correct callstack in its debugger if I make sure to ask it to "Pause on caught exceptions".

image

1 Like

Hmmm, you're right, nvm, not sure what I saw. Was it always like that? Maybe an async stack threw me off; but that only adds to the stack when expanded, it looks like.

Yea, it’s always been like that afaik.

Didn't Edge (pre-Chromium) and old Safari initially set it on throw? I do recall a long time ago running into that with PhantomJS where I was getting undefined before throw and a string after. Doesn't reproduce on current iOS Safari, though.

May be worth checking behavior in various engines, especially outside the main ones (V8, JavaScriptCore, SpiderMonkey, and ChakraCore), to verify what everyone does.

IE up to v11 needs a throw statement to attach a stack trace (we still have a code path to handle that scenario in ospec). No idea for Edge and Safari.

I ran into the issue, I knew I was seeing something funky. I just didn't know how to reproduce it, but now I can any time by making Solid.js re-throw any error.

The call stack is async.

This is what I see when I pause on the line that re-throws the error:

This is what I see in the console:

Opening the first dropdown shows this:

Opening the second drop down shows the original error unformatted:

The handleError function is in a try-catch block like this:

  try {
    nextValue = node.fn(value);
  } catch (err) {
    handleError(err);
  }

As you can tell by these screenshots, Chrome is showing the trace for the location where the throw happens, not the trace for the re-thrown error.


EDIT: Oh, hmm. Maybe it has to do with the DeveloperError being defined like this:

function DeveloperError(message) {
  this.name = "DeveloperError";
  this.message = message;

  //Browsers such as IE don't have a stack property until you actually throw the error.
  let stack;
  try {
    throw new Error();
  } catch (e) {
    stack = e.stack;
  }

  this.stack = stack;
}

if (defined(Object.create)) {
  DeveloperError.prototype = Object.create(Error.prototype);
  DeveloperError.prototype.constructor = DeveloperError;
}

DeveloperError.prototype.toString = function () {
  let str = `${this.name}: ${this.message}`;

  if (defined(this.stack)) {
    str += `\n${this.stack.toString()}`;
  }

  return str;
};