function level try/catch

Hi,
have anyone thought about this?

try function fnName() {
    ...
} catch() {
    ....
}

it would fit to other features well

try async function fnName1() { ... } catch() { ... } 
const fnName2 = try () => { ... } catch() { ... } 

"try async function" even sounds right to me. What do you think?

Hi @ondrejvelisek!

What do you see as the main problem this is solving?

The only benefit I can see is for catching default assignments, as there is no way of catching those at the moment.

// not possible to catch this  ->  vvvvvvvvvvvvvvvvvv ❌
function functionName(arg1, arg2 = functionMightThrow(arg1)) {
  try {
  } catch (err) {
    // but I can access function arguments here ✅
  }
}
try function functionName(arg1, arg2 = functionMightThrow(arg1)) {
  ...
} catch (err) {
  // can catch `functionMightThrow` ✅
  // but no access to function arguments ❌
}
2 Likes

I was thinking just about readabíity. Did not think about catching default arguments. What a great point! it syntactically make sense, since arguments follows "try" keyword. I'm a bit afraid catch block without arguments would be a bit weak since developer would not be able to log them. But still could be usefull in some cases.

How is this more readable?

When i look at it, i don’t know if this is running the try/catch on invocation or at definition time.

How would it work on async functions? The catch would be outside the expected await boundary, but async functions only reject and never throw.

2 Likes

Another great point about missleading invocation vs. definition context. Thanks for that.

Where do you see the problem with async functions? Could you place an example?

an async function never produces an exception, only a rejected promise - so the catch wouldn't have anything to catch. If it did implicitly await the rejected promise and catch it, it'd be weird to be able to use await in that catch block outside the body of the async function.

1 Like

I see.

try async function fnName() {
    ...
} catch() {
    await someAsyncFn(); // this await seems weird
}

Do you see some elegant option to solve it?

I think the elegant solution is to have the try/catch inside the function, as already works.

2 Likes

I think there are more blocks could be "catchful" without the need of wrapping them in try block. It's just a redundant construction in some cases.

for await (const chunk of stream) {
  // throw something
}
catch (err) {
 // handle error
} 
finally {
  stream.close()
}

The logic looks pretty straightforward: if a block has catch or finally statement it assumed as wrapped with try.

1 Like

I would strongly vote for keeping the try keyword e.g.

try for await (const chunk of stream) {
  // throw something
}
catch (err) {
 // handle error
} 
finally {
  stream.close()
}

However great idea to generalize it for other code blocks. Thanks.

fyi callers shouldn't need to close streams in finally blocks. Iterators either complete (done: true) or get an early return.

const stream = {
    [Symbol.iterator]() {
        const values = [1, 2, 3][Symbol.iterator]();
        return {
            next: values.next.bind(values),
            return() {
                console.log('clean up stream early');
            },
        }
    }
}

for (const v of stream) {
  console.log(v);
  if (v === 2) {
      throw new Error('test');
  }
}
// logs: 1, 2, clean up steam early

Also somewhat related is the Explicit Resource Management proposal as it also builds on the try keyword GitHub - tc39/proposal-explicit-resource-management: ECMAScript Explicit Resource Management

try using (const handle = acquireFileHandle()) { // critical resource
    ...
} // implicit cleanup