-
-
Notifications
You must be signed in to change notification settings - Fork 16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Question: Why Symbol.result in addition to throw? #5
Comments
That's how mostly of our new features are actually introduced into JS.
And a lot of other examples... Basically, every new syntax is backed up by something set in the prototype. |
I think this is different than other proposals that add new symbol properties. Not every new syntax involves symbols - It seems like a try-expression would be a nice, ergonomic solution unless there are issues that make it infeasible. |
Would it be worth it to add this to the language where we can already do: const [error, value] = try(() => null.foo); The benefit is just marginal for legitimizing a very questionable pattern. |
I don't know if any other current or former members of TC39 have commented anywhere on this yet, but the symbol interface is going to be a non-starter for this proposal. There's nothing gained by having it, and it makes the proposal substantially less useful. I would drop that and make this a simple syntactic sugar if you'd like it to go anywhere. let [error, result] ?= expression
// is just syntactic sugar for:
let $0
try {
$0 = [void 0, expression]
} catch ($1) {
$0 = [$1, void 0]
}
let [error, result] = $0 (This is not an endorsement of the overall idea here though, I personally would not like to add Golang style error returns to JavaScript in any form, in favor of other proposals such as try-catch-expressions) |
Another drawback of const [, data] ?= f(); and const data = f(); give the same output (assuming there is no error/exception), it could technically do two completely different things. The fact that it is even possible sounds like a terrible idea for something that should just be syntactic sugar around |
to be clear, that's not just a drawback, that's the reason that a symbol for this will literally never fly in committee. It should just be removed. |
This is effectively an overload for the call operaror |
Another problem with symbols in this case is: How do we know a function implements This is potentially solved in #5 (comment) using #4 if all this becomes a sugar for try, which I'd love to see |
following @jamiebuilds example. The try-expression can be parsed easily to this: // try-expression
const [error, value] = try expression;
// can be parsed to:
let result;
try {
result = [null, expression];
} catch (e) {
result = [e];
}
const [error, value] = result; Some examples: // try-expression with await promise
const [error, value] = try await Promise.resolve("hello!");
// can be parsed to:
let result;
try {
result = [null, await Promise.resolve("hello!")];
} catch (e) {
result = [e];
}
const [error, value] = result; // try-expression with await no promise
const [error, value] = try await "not a promise";
// can be parsed to:
let result;
try {
result = [null, await "not a promise"];
} catch(e) {
result = [e];
}
const [error, value] = result; // try-expression unassigned
try "expression";
// can be parsed to:
try {
"expression"; // NOTE: expression may produce side effects, still execute expression
} catch {} // try-expression of an object (this needs some strict syntax, for not be confused with try statement)
const [error, value] = try ({property: "hello!"}); // enclosed with parentheses
// can be parsed to:
let result;
try {
result = [null, ({property: "hello!"})];
} catch (e) {
result = [e];
}
const [error, value] = result; There is no need for That is my opinion :) I really like the work of this proposal, and I would like to see this feature in the soon future. This would also solve this issue #39 (await type safety) by allowing any expression be used in the "try-expression" EDIT: Some problems with not assigned try-expressionsA not assigned try-expression will have the problem that the error will be completely dropped. Leading to undesired behaviour (or not). In Go error handling, the error and the value(s) can be unassigned (and that is good, I guess). |
The allowance of Example: // malicious program/library/framework/etc.
Function.prototype[Symbol.result] = function() {/* does nothing */}
function libFunc() {
throw "You have been crashed :P";
}
// user program
const [error, value] ?= libFunc(); // Does not create the tuple, instead program crashes |
Try expression proposed syntax:
TryExpression would become a UnaryExpression, just like AwaitExpression and similars. This would have a drawback and that is it can be misused by chaining TryExpression unary expressions. Although AwaitExpression also have this drawback and it went to the Standard. Example: const [error, value] = try try func();
// reinterpreted as this:
const [error, value] = try (try func());
// would be parsed to:
let result;
try {
let result2;
try {
result2 = [null, func()];
} catch (e) {
result2 = [e];
}
result = [null, result2]; // result2 is a expression that will never throw
} catch (e) {
// UNREACHABLE
result = [e];
}
const [error, value] = result; // error === null and value === TupleNotActualValue If As I said AwaitExpression also have this drawback, it can also be chained await await await 123; // produces 123 |
I think we all agree that Regarding this:
I think one option is to not allow not assigned try expressions. This could either be an eslint rule (which I think it's the best case) or a compiler error where |
To be clear, by "Try Expressions" I was not suggesting a "Try Operator":
Do ExpressionsIf you're unfamiliar, this has been a TC39 proposal for a long time, I believe it has a lot of support, but it's been stuck for awhile on implicitly returning object literals (and maybe some other things) let sum = do {
let augend = 40
let addend = 2
augend + addend // implicit "return"
}
console.log(sum) // >> 42 Try Operator vs Try Expression ComparisonSyntax// Try Operator
let [error, result] = try await fetch(url).then(res => res.json())
if (error != null) {
window.reportError(error)
result = 42
}
// Try Expression
let result = try {
await fetch(url).then(res => res.json())
} catch (error) {
window.reportError(error)
42
} Try Operator:
Try Expression:
Finalizer ComparisonTry Operator You need to do it specifically in this order, which flips the order of what people are used to in JavaScript. And you now need extra code to ensure the error is re-thrown to match the way that let [error, result] = try (db.open(), db.query("SELECT RANDOM"))
db.close()
if (error != null) {
throw error
} Try Expression Not really anything new here. let result = try {
db.open()
db.query("SELECT RANDOM()")
} finally {
db.close()
} imoFor the above reasons, I strongly support "Try Expressions" and I don't support a "Try Operator" (including other forms like a new assignment operator or other words for I haven't put a ton of thought into it (I'm not sure of all it's implications on parsing), but I might support building upon // try statement, with body expression
let result
try result = doSomething() catch (error) {
// ...
}
// try expression, with body expression
let result = try doSomething() catch (error) {
// ...
} |
I have created an npm package to handle this, using a function wrapper instead of the new operator, so it is backward complicated. |
What if I've also written the similar library ) |
when throw null, consider null as a special error type |
It is not a good idea to presume the error type before it has been caught, please consider: In the when you run only your own code and you are confident that only const [error, value] = safeTry<null>(runOnlyOwnCode);
if (error === null) {
console.error("The error has been thrown");
throw new Error('Real error');
}
// process with value here The handling of nulish and falsy errors is one of the issues of the safe-assignment and try-expression. |
@DScheglov |
The problem is not in the declared type of error. The declaration of this type -- is a problem. We cannot expect that only expected type exceptions will be thrown. That's why typescript goes to |
@DScheglov so the default generic Error type in my package is
|
let's guess you call some function. How do you know what type of exception it can throw? Ok, you can look at the code of the function you explicitly call. But this function can call another one, the last one calls the third one, and so on. Before answering on my question, please read the following comment. We've started discusion of your library, so it is better to move this discussion to the correspondent repo. |
Most of the language of this proposal is centered around improving try/catch ergonomics which sounds great! As such, I'm curious why this adds so much additional behavior focused around
Symbol.result
.My naïve expectation for this sort of language feature would be to literally extend try to accept an expression and return a result tuple/object representing either the resulting or thrown value of the expression.
Example:
I assume there's a good reason this proposal opted for adding a new error handling paradigm instead so I'd like to hear more about that.
The text was updated successfully, but these errors were encountered: