Skip to content

Commit

Permalink
feat: improved readme and docs
Browse files Browse the repository at this point in the history
  • Loading branch information
arthurfiorette committed Aug 12, 2024
1 parent c643b01 commit 2658ba5
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 65 deletions.
3 changes: 3 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"semi": false
}
132 changes: 73 additions & 59 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,37 @@
<br />

<h1>ECMAScript Error Safe Assignment Operator</h1>
<h1>ECMAScript Safe Assignment Operator</h1>

> [!WARNING]
> This proposal is actively being developed and [any help is welcome](#help-us-to-improve-this-proposal).
<br />

This proposal introduces a new operator `/*?*/=` _(Error Safe Assignment)_ that transforms the function result into a `[error, null]` tuple if the function throws an error or `[null, result]` if the function returns a value successfully. This operator also works with promises, async functions and any object that implements the [`Symbol.result`](#symbolresult) method.
This proposal introduces a new operator `?=` _(Safe Assignment)_ that transforms the function result into a `[error, null]` tuple if the function throws an error or `[null, result]` if the function returns a value successfully. This operator also works with promises, async functions and any object that implements the [`Symbol.result`](#symbolresult) method.

For example, when doing any I/O operation or interacting with any Promise-based API, it can fail and **it will** fail in the most unexpected ways at runtime. Forgetting to
handle these errors can lead to unexpected behavior and security vulnerabilities.

<br />

```ts
const [error, response] ?= await fetch("https://arthur.place")
```

<hr />
<br />

- [Motivation](#motivation)
- [Proposed features](#proposed-features)
- [`Symbol.result`](#symbolresult)
- [The safe assignment operator (`/*?*/=`)](#the-safe-assignment-operator-)
- [The safe assignment operator (`?=`)](#the-safe-assignment-operator-)
- [On functions](#on-functions)
- [On objects](#on-objects)
- [Recursive handling](#recursive-handling)
- [Promises](#promises)
- [`using` statement](#using-statement)
- [Comparison](#comparison)
- [Try/Catch is not enough](#trycatch-is-not-enough)
- [Comparison](#comparison)
- [Prior Art](#prior-art)
- [What this proposal DOES NOT aim to solve](#what-this-proposal-does-not-aim-to-solve)
- [Current limitations](#current-limitations)
Expand All @@ -37,7 +47,7 @@ handle these errors can lead to unexpected behavior and security vulnerabilities
- **Error Handling**: Simplify error handling by avoiding try-catch blocks.
- **Readability**: Improve code readability by reducing nesting.
- **Consistency**: Make error handling consistent across different APIs.
- **Security**: The more easy is to handle errors, the less likely is to forget to do it.
- **Security**: Make it harder to forget to handle errors.

<br />

Expand All @@ -47,7 +57,7 @@ How often have you seen code like this?

```ts
async function getData() {
const response = await fetch('https://api.example.com/data')
const response = await fetch("https://api.example.com/data")
const json = await response.json()
return validationSchema.parse(json)
}
Expand All @@ -60,7 +70,7 @@ The problem is that the above function can crash your program but doesn't feel t
3. `parse` can throw
4. Each of these may throw more than one type of error

As such, we propose the adoption of a novel operator `/*?*/=` that allows for a more concise and readable error handling.
As such, we propose the adoption of a novel operator `?=` that allows for a more concise and readable error handling.

```ts
async function getData() {
Expand Down Expand Up @@ -101,9 +111,11 @@ Please read the [what this proposal DOES NOT aim to solve](#what-this-proposal-d

Below is a list of features that this proposal aims to introduce:

<br />

### `Symbol.result`

Any object that implements the `Symbol.result` method can be used with the `/*?*/=` operator.
Any object that implements the `Symbol.result` method can be used with the `?=` operator.

```ts
class Division {
Expand All @@ -128,9 +140,9 @@ The return of the `Symbol.result` method must be a tuple with the first element

<br />

### The safe assignment operator (`/*?*/=`)
### The safe assignment operator (`?=`)

The `/*?*/=` operator calls the `Symbol.result` method of the object or function on the right side of the operator.
The `?=` operator calls the `Symbol.result` method of the object or function on the right side of the operator.

```ts
const obj = {
Expand All @@ -156,18 +168,18 @@ The result should match the format of `[error, null | undefined]` or `[null, dat

#### On functions

If the `/*?*/=` operator is used in a function, all used parameters are passed to the `Symbol.result` method.
If the `?=` operator is used in a function, all used parameters are passed to the `Symbol.result` method.

```ts
declare function action(argument: string): string

const [error, data] /*?*/= action(argument1, argument2, ...)
const [error, data] ?= action(argument1, argument2, ...)
// const [error, data] = action[Symbol.result](argument, argument2, ...)
```

#### On objects

If the `/*?*/=` operator is used in an object, nothing is passed to the `Symbol.result` method as parameters.
If the `?=` operator is used in an object, nothing is passed to the `Symbol.result` method as parameters.

```ts
declare const obj: { [Symbol.result]: unknown }
Expand Down Expand Up @@ -214,15 +226,15 @@ These cases may go from 0 to 2 levels of objects with `Symbol.result` methods, a

### Promises

A Promise is the only other implementation besides `Function` that can be used with the `/*?*/=` operator.
A Promise is the only other implementation besides `Function` that can be used with the `?=` operator.

```ts
const promise = getPromise()
const [error, data] ?= await promise
// const [error, data] = await promise[Symbol.result]()
```

You may have noticed that we might have the usecase of `await` and `/*?*/=` together, and that's fine. Since there's a [recursive handling](#recursive-handling), there's no problem in using them together.
You may have noticed that we might have the usecase of `await` and `?=` together, and that's fine. Since there's a [recursive handling](#recursive-handling), there's no problem in using them together.

```ts
const [error, data] ?= await getPromise()
Expand All @@ -241,7 +253,7 @@ Where the execution will follow this order

### `using` statement

The `using` or `await using` statement should also work with the `/*?*/=` operator. Everything using does in a normal `using x = y` statement should be done with the `/*?*/=` operator.
The `using` or `await using` statement should also work with the `?=` operator. Everything using does in a normal `using x = y` statement should be done with the `?=` operator.

```ts
try {
Expand All @@ -251,7 +263,7 @@ try {
}

// now becomes
using [error, a] /*?*/= b
using [error, a] ?= b

// or with async

Expand All @@ -262,49 +274,13 @@ try {
}

// now becomes
await using [error, a] /*?*/= b
await using [error, a] ?= b
```
Where the `using management flow` is only applied when `error` is `null | undefined` and `a` is truthy and has a `Symbol.dispose` method.
<br />
## Comparison

The `/*?*/=` neither `Symbol.result` proposal introduces new logic to the language, in fact we can already reproduce everything that this proposal does with the current, _but verbose and forgetful_, language features:

```ts
try {
// try expression
} catch (error) {
// catch code
}

// or

promise
.then((data) => {
// try code
})
.catch((error) => {
// catch code
})
```

is equivalent to:

```ts
const [error, data] ?= expression

if (error) {
// catch code
} else {
// try code
}
```

<br />

## Try/Catch is not enough
<!-- credits to https://x.com/LeaVerou/status/1819381809773216099 -->
Expand All @@ -321,7 +297,7 @@ Using `try/catch` blocks has **two main syntax problems**:
// Nests 1 level for each error handling block
async function readData(filename) {
try {
const fileContent = await fs.readFile(filename, 'utf8')
const fileContent = await fs.readFile(filename, "utf8")

try {
const json = JSON.parse(fileContent)
Expand All @@ -343,7 +319,7 @@ async function readData(filename) {
let json

try {
fileContent = await fs.readFile(filename, 'utf8')
fileContent = await fs.readFile(filename, "utf8")
} catch (error) {
handleFileError(error)
return
Expand All @@ -362,6 +338,42 @@ async function readData(filename) {
<br />
## Comparison
The `?=` neither `Symbol.result` proposal introduces new logic to the language, in fact we can already reproduce everything that this proposal does with the current, _but verbose and forgetful_, language features:
```ts
try {
// try expression
} catch (error) {
// catch code
}

// or

promise
.then((data) => {
// try code
})
.catch((error) => {
// catch code
})
```
is equivalent to:
```ts
const [error, data] ?= expression

if (error) {
// catch code
} else {
// try code
}
```
<br />
## Prior Art
As we can see, this so loved pattern is already present in many languages:
Expand All @@ -378,6 +390,7 @@ As we can see, this so loved pattern is already present in many languages:
- _And many others..._
<br />
## What this proposal DOES NOT aim to solve
- **Handle errors for you**: This proposal aims to facilitate error handling, however you must still write the code to handle it, the proposal just makes it easier to do so.
Expand Down Expand Up @@ -434,15 +447,16 @@ This proposal is in its early stages and we need your help to improve it. Please
## Authors
- [Arthur Fiorette](https://github.com/arthurfiorette)
- [Arthur Fiorette](https://github.com/arthurfiorette) <sub>([Twitter](https://x.com/arthurfiorette))</sub>
<br />
## Inspiration
- This tweet from @LaraVerou: https://x.com/LeaVerou/status/1819381809773216099
- Effect TS Error management: https://effect.website/docs/guides/error-management
- [This tweet from @LaraVerou](https://x.com/LeaVerou/status/1819381809773216099)
- [Effect TS Error management](https://effect.website/docs/guides/error-management)
- The [`tuple-it`](https://www.npmjs.com/package/tuple-it) npm package, which introduces a very similar concept, but often adds properties to `Promise` and `Function` prototypes, which is not ideal.
- The easiness of forgetting to handle errors in Javascript code.
<br />
Expand Down
2 changes: 1 addition & 1 deletion example.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Proposal: Error-Safe Assignments
// Proposal: Safe Assignments
//
// Be able to handle errors in assignments without throwing exceptions, in a
// easier way and without let variables + nesting try/catch blocks.
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
{
"private": true,
"name": "proposal-error-safe-assignments",
"name": "proposal-safe-assignment-operator",
"description": "A repository template for ECMAScript proposals.",
"scripts": {
"start": "npm run build-loose -- --watch",
"build": "npm run build-loose -- --strict",
"build-loose": "node -e 'fs.mkdirSync(\"build\", { recursive: true })' && ecmarkup --load-biblio @tc39/ecma262-biblio --verbose spec.emu build/index.html --lint-spec"
},
"homepage": "https://github.com/arthurfiorette/proposal-error-safe-assignments#readme",
"homepage": "https://github.com/arthurfiorette/proposal-safe-assignment-operator#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/arthurfiorette/proposal-error-safe-assignments.git"
"url": "git+https://github.com/arthurfiorette/proposal-safe-assignment-operator.git"
},
"license": "MIT",
"devDependencies": {
Expand Down
4 changes: 2 additions & 2 deletions spec.emu
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.4/styles/github.min.css">
<script src="./spec.js"></script>
<pre class="metadata">
title: ECMAScript Error Safe Assignment Operator
title: ECMAScript Safe Assignment Operator
stage: -1
contributors: Arthur Fiorette
</pre>
Expand All @@ -13,7 +13,7 @@ contributors: Arthur Fiorette

<emu-intro id="intro">
<h1>Introduction</h1>
<p>This proposal introduces syntax and semantics for error safe assignments</p>
<p>This proposal introduces syntax and semantics for safe assignments</p>
</emu-intro>

<emu-clause id="sec-ecmascript-data-types-and-values" aoid="Type">
Expand Down

0 comments on commit 2658ba5

Please sign in to comment.