Skip to content
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

Alternative Approach: Implementing Safe Assignment Without ?= Operator #24

Closed
rafageist opened this issue Aug 20, 2024 · 12 comments
Closed

Comments

@rafageist
Copy link

rafageist commented Aug 20, 2024

Hi everyone,

First of all, I appreciate the effort and thought that has gone into the proposal for the ?= operator. It's clear that the intention is to simplify and standardize error handling in JavaScript, which is an important goal.

However, I believe that this proposal might not be necessary given that we can achieve similar results using existing JavaScript features. Specifically, we can handle errors and return results in a concise and readable way by using simple wrapper functions. Here's an example of how this can be done:

const synco = (operation) => {
   let result = null;
   let error = null;

   try {
      result = operation(); 
   } catch(err) {
      error = err;
   }

   return [error, result];
};

const asynco = async (operation) => {
   let result = null;
   let error = null;

   try {
      result = await operation();
   } catch(err) {
      error = err;
   }

   return [error, result];
};

Moreover, if we use our imagination, we can extend these functions to return not just tuples, but also objects or any other robust structures that might suit the specific needs of a given application. This flexibility allows developers to tailor their error handling to the exact requirements of their projects, without being constrained to a single approach. For example, we could return an object with additional metadata, logs, or context about the error and the operation, providing a more comprehensive solution.

Sync example

// Operation to be executed synchronously
const parseJson = () => {
   const data = JSON.parse('{"key": "value"}'); // Example of a potentially error-prone operation
   return data.key;
};

// Execute the operation using synco
const [error, result] = synco(parseJson);

if (error) {
   console.error('An error occurred:', error.message);
} else {
   console.log('Result:', result); // Output: "Result: value"
}

Async example

// Operation to be executed asynchronously
const fetchData = async () => {
   const response = await fetch('https://api.example.com/data');
   const data = await response.json();
   return data.key;
};

// Execute the operation using asynco
const [error, result] = await asynco(fetchData);

if (error) {
   console.error('An error occurred:', error.message);
} else {
   console.log('Result:', result); // Output: the value of data.key if successful
}

Key Points:

  • Simplicity: These functions encapsulate the try-catch logic, returning an error-result tuple similar to what the ?= operator aims to achieve.
  • Compatibility: Both sync and async operations are handled in a clean and straightforward manner.
  • No Language Changes Needed: This approach doesn't require any changes to the JavaScript language, and it can be easily implemented today without waiting for language updates.

Conclusion:

While the ?= operator is an interesting idea, it's important to consider whether introducing a new operator is justified when we can already accomplish the same goals with existing language features. The above functions provide a simple, readable, and effective way to handle errors without adding complexity to the language.

I'd love to hear thoughts from the community on this approach and whether it might be a sufficient alternative to the proposed operator.

Thanks for considering my input!

@anacierdem
Copy link

Also see #9

@rafageist
Copy link
Author

Also see #9

@anacierdem I reviewed the discussion in issue #9 before this, which effectively addresses potential problems with the ?= operator and explores alternative solutions. While that discussion focuses on the risks and possible workarounds, my issue emphasizes that we can achieve the same goals with current JavaScript features, without needing any new additions to the language. If the intent is to simplify error handling, we already have robust patterns available that don't introduce extra complexity.

@rafageist
Copy link
Author

@arthurfiorette

Important note:

While the idea of using an assignment operator like ?= for error handling is intriguing, I believe error management should be handled at a higher level than what the interpreter or compiler directly manages. Typically, assignment operators are focused on straightforward value assignment, whereas error handling involves more complex logic. Keeping these responsibilities separate helps maintain clarity and avoid overcomplicating the language. By placing error handling in its own layer, we can maintain cleaner, more maintainable code without burdening basic language constructs.

@arthurfiorette
Copy link
Owner

arthurfiorette commented Aug 22, 2024

By placing error handling in its own layer, we can maintain cleaner, more maintainable code without burdening basic language constructs.

A lot of words that don't make sense without a specific context.

There are a lot of languages with operators related to errors, Rust's ? is a example of that. Also, the try operator won the syntax contest.

@khaosdoctor
Copy link

As it was said before in several other issues, I don't think that the fact that something can be achieved with current constructs should be a blocker for something to work better with a new construct. Several things can be achieved just using JS, array/object grouping, set operators (intersect, union, etc) but still, they've made in as proposals.

IMHO, the whole goal of a proposal is to improve on existing syntax, so things we do often should be improved on by creating constructs around the language that make those things more ergonomic.

@rafageist
Copy link
Author

@arthurfiorette @khaosdoctor

The TC39 technical committee will carry out a rigorous analysis. I leave you with aspects that I believe they will review: #33

@TheUniqueMathemagician
Copy link

I’ll join @khaosdoctor on this. Just because a feature already exists doesn’t mean we can’t try to improve it. Before async/await, there were Promises, which were more difficult to understand than the new syntax. Now everyone uses async/await and, I agree, sometimes still has to fall back to the old Promise model in some rare cases to handle old API callback patterns. That said, the new syntax is much more maintainable and faster. With this proposal, we can write in one line what previously took several lines.

Even though try/catch makes the errors more visible than a simple ?: operator, it has a flawed design in terms of scoping. This often forces developers to write the whole code inside the try block because it depends on a value declared within it, which could throw something else, so the try catch is more like a global "please, prevent my code from crashing in production", than a real tool as it was designed for.

try {
    const result = await fetch(...);

    /* logic here that could throw something else */
} catch {
    /* handle more than one thrown error */
}

Sometimes, it becomes even worse in terms of readability, as people start to:

  • nest try/catch blocks
  • handle multiple errors in one global try/catch,
  • declare variable outside the scope it was assigned
  • combine try/catch with async/await and promises
  • catch the errors only to console.log them
  • ...

@rafageist
Copy link
Author

@TheUniqueMathemagician @arthurfiorette @anacierdem @khaosdoctor

You will always need to:

  • Try a block of instructions, either with an ECMA block, or encapsulated in some magic function, specify with some operator, or any other syntax that complies with ECMA

  • Ask if the exception occurred (branch)

  • Handle or not the error that occurred

What options do you have for this?

  • The current try-catch solution that is not going to change (it is the same solution as C++, CSharp, Java, PHP, etc.)

  • Add another way to catch exceptions, for which people will wonder why there are 2 ways.

The Promise catch still exists and is a method, just like if you use a wrapper function. The concepts of function and method exist and can be reused. The current try-catch works for both async and sync issues.

So, it is a misconception that "safe assignment" #32 is safe. Assignment is always safe. And I think it is too much responsibility for an operator.

The first question that the TC39 committee asks, and it is a requirement that they ask you, is if what you are trying to add can already be solved with what already exists. According to the rules:

Ideation and exploration. Define a problem space in which the committee and the champions can focus their efforts.
- Make the case for an improvement
- Describe the shape of some possible solutions
- Identify potential challenges
- Research how the problem is dealt with using available facilities today
- Research how the problem has been solved by other languages or in the library ecosystem

When you start to let your imagination fly, many alternatives appear (complying with ECMA):

// 1. inline solution

try let result = await fetch(...) catch err
try result = await fetch(...) catch err
try result = await fetch(...)
let result = await fetch(..) catch err

// ...


// 2. catch like switch-case
try:
// ...
catch err:
 // ...
finally:  // (or finally; / break; to finish...)
// ...

// 3. catch for any ECMA blocks (anonymous,, functions, cases, classes, if, ...)

{   ....   } catch (err) {   } finally {   ...   }

function foo() {  } catch (err) {  } finally {   ...   }

if (...) {   } catch {   } finally {   ...   }

class Person { ..... } catch (err) { ... } finally {   ...   } // if something happens inside the class

.... I want to go back inside the block!!!

But when you think about it you end up asking yourself: is it worth it? Would it be better to continue using the current try catch?

You can also think about improving the current try-catch. I've thought about it myself and still question myself every day, whether it's worth it or whether to continue using a switch inside the catch: https://github.com/rafageist/proposal-multiple-catch-when

In the end, what is there is probably more than enough and there is no need to create problems where there are none. The traditional try-catch is a proven solution. An assignment operator should not have the responsibility of handling exceptions. If you want a weird solution that is semantically understood as "safe assignment", do something like this and you won't need an if:

catch(response = await fetch(...)) { 
  // response is an exception here
  // ... return!
}

// MAYBE response is not an exception here

The challenge I see is that if you don't get out with a return in the catch, you always have to ask down below if there was an error or not.

When you review all those alternatives and find problems you end up concluding that try-catch already solves it.

@khaosdoctor
Copy link

khaosdoctor commented Aug 26, 2024

In the end, what is there is probably more than enough and there is no need to create problems where there are none. The traditional try-catch is a proven solution.

You could argue that callbacks are also a proven solution, and yet, we have promises, which are also another proven solution with then/catch, and yet, we have async/await. We can go even further, assembly is a proven solution, yet we have C (another proven solution), yet we have CPP, Go, Rust, and JavaScript. If proven solutions moved the world, we would be discussing C headings here on our SVN servers from our Telex machines.

Clinging on the idea that "if it works leave the way it is" is really not what drives innovation, and certainly not (at least for me) the way the JS community has been behaving since forever. We have multiple examples of "good enough" solutions that were replaced by incredible solutions (jQuery, MooTools, Lodash, Underscore), much of those not only inspired but were literally copied into the spec. There's always room for improvement, even in proven solutions.

Add another way to catch exceptions, for which people will wonder why there are 2 ways.

But here's the thing, there won't be two ways. If we look at #4, using Symbol.result would be indeed a problem since previous functions like JSON.parse would need to be updated to include that symbol, however, transforming the operator into a syntax sugar like async/await, you won't create a new way to handle errors, you will hide the current way behind a curtain.

People can use both of them the same way Node has fs and fs/promises, the same way a third of JS's APIs uses callbacks (setTimeout, setInterval) and the other third doesn't (like fetch, async iterators...) and the final third is event-based (streams, mouse events, XHRHttpRequest, etc).

Do you wonder about why are there three completely different paradigms to handle data in JS very often? I really don't, and I don't believe other users will do so because of try/catch (which is already considered a bad pattern).

do something like this and you won't need an if

So the problem is the if? Because try/catch already works as branching, it's the closest thing to an if/else we have besides switch... That solution you proposed is not bad at all for me tbh, the problem is that it touches way more stuff than just a single part of the language, we are talking about expressions and other stuff... It's way more complex to implement that, and thus, to make a case for it...

Your points are valid, but they're not strong enough for me to justify ditching this improvement. Especially since other languages like Go already implement the same pattern and are proven solutions

@rafageist
Copy link
Author

In the end, what is there is probably more than enough and there is no need to create problems where there are none. The traditional try-catch is a proven solution.

@khaosdoctor

I was just giving some additional considerations. But my arguments from the beginning are 2:

  1. The current language allows to achieve the same thing without the need for an operator (this issue)

  2. An assignment operator should not have the responsibility of flow control. The most that has been achieved is conditional assignment. An exception is a branch in the program flow (issue Reconsidering the Need for a New Operator in Error Handling Proposals #33)

Both arguments will be raised by the TC39 committee. For this reason I thought of valid language alternatives such as control structures, not assignment operators.

@khaosdoctor
Copy link

khaosdoctor commented Aug 27, 2024 via email

@rafageist
Copy link
Author

I have created an issue #37 dedicated to the critical point in question. I am closing this one because I believe everything has been addressed and it is up to the developers and TC39 to resolve it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants