Skip to content

Commit

Permalink
Thinking in Effection page and reorganize page (#846)
Browse files Browse the repository at this point in the history
* Adding draft of Thinking in Effection page

* Started writing the roseta stone section

* Added running to completion demo

* Corrected the example image

* Improved Every function exists fully

* Added text for No Operation runs longer than it's parent

* Minor text adjustments

* Added Charle's comments

* Grammar, spelling, and rhythm pass

* Renamed file, removed block quote and image

---------

Co-authored-by: Charles Lowell <[email protected]>
  • Loading branch information
taras and cowboyd authored Dec 15, 2023
1 parent 171b4ae commit 4fa896a
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 14 deletions.
13 changes: 1 addition & 12 deletions www/docs/actions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -113,21 +113,10 @@ async function fetch(url) {
}
```

Consulting the "async rosetta stone" below, we can substitute the async
Consulting the [Async Rosetta Stone](), we can substitute the async
constructs for their Effection counterparts to arrive at a line for line
translation.

| Async | Effection |
|-----------------|-----------------------|
|`Promise` | `Operation` |
|`new Promise()` | `action()` |
|`await` | `yield*` |
|`async function` | `function*` |
|`AsyncIterable` | `Stream` |
|`AsyncIterator` | `Subscription` |
|`for await` | `for yield* each` |


```js
function* fetch(url) {
return yield* action(function*(resolve, reject) {
Expand Down
12 changes: 12 additions & 0 deletions www/docs/async-rosetta-stone.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@


| Async/Await | Effection |
| ---------------- | ----------------- |
| `Promise` | `Operation` |
| `new Promise()` | `action()` |
| `await` | `yield*` |
| `async function` | `function*` |
| `AsyncIterable` | `Stream` |
| `AsyncIterator` | `Subscription` |
| `for await` | `for yield* each` |

8 changes: 6 additions & 2 deletions www/docs/structure.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@
"Getting Started": [
["introduction.mdx", "Introduction"],
["installation.mdx", "Installation"],
["typescript.mdx", "TypeScript"],
["thinking-in-effection.mdx", "Thinking in Effection"],
["async-rosetta-stone.mdx", "Async Rosetta Stone"]
],
"Learn Effection": [
["operations.mdx", "Operations"],
["actions.mdx", "Actions and Suspensions"],
["resources.mdx", "Resources"],
["spawn.mdx", "Spawn"],
["collections.mdx", "Streams and Subscriptions"],
["events.mdx", "Events"],
["errors.mdx", "Error Handling"],
["typescript.mdx", "TypeScript"]
["errors.mdx", "Error Handling"]
],
"Advanced": [
["scope.mdx", "Scope"],
Expand Down
113 changes: 113 additions & 0 deletions www/docs/thinking-in-effection.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
When we say that Effection is "Structured Concurrency and Effects for Javascript" we mean three things:

1. No operation runs longer than its parent.
2. Every operation exits fully.
3. It's just JavaScript, and except for the guarantees derived from (1) and (2), it should feel familiar in every other
way.

Developing a new intuition about how to leverage Structured Concurrency, while leaning on your existing intuition as a
JavaScript developer will help you get the most out of Effection and have you attempting things that you would never
have even dreamed before.

## No operation runs longer than its parent.

In JavaScript, developers rarely have to think about memory allocation because memory lifetime is bound to the scope of
the function that it was allocated for. When a function is finished, its scope is torn down and all of the memory
allocated to variables in function's scope are released safely. Binding memory management to scope gives developers the
freedom to focus on their application instead worrying about leaking memory.

Structured concurrency establishes the same relationship between scope and asynchrony. Every Effection operation is
bound to the lifetime of its parent. Because of this, Effection automatically tears down
child operations when the parent operation completes or is halted. Like with memory management, binding asynchrony to
scope frees developers to focus on writing their applications instead of worrying about where and when to run
asynchronous cleanup.

The key to achieving this freedom is to make the following mental shift about the natural lifetime of an asynchronous
operation.

**before**

> An asynchronous operation will run as long as it needs to
**after**

> An asynchronous operation runs only as long as it's needed.
Effection provides this shift. Whenever an operation completes, none of its child operations are left
around to pollute your runtime.

## Every operation exits fully.

We expect synchronous functions to run completely from start to finish.

```js {5} showLineNumbers
function main() {
try {
fn()
} finally {
// code here is GUARANTEED to run
}
}
```

Knowing this makes code predictable. Developers can be confident that their functions will either return a result or
throw an error. You can wrap a synchronous function in a `try/catch/finally` block to handle thrown errors. The
`finally` block can be used to perform clean up after completion.

However, the same guarantee is not provided for async functions.

```js {5} showLineNumbers
async function main() {
try {
await new Promise((resolve) => setTimeout(resolve, 100,000));
} finally {
// code here is NOT GUARANTEED to run
}
}

await main();
```

Once an async function begins execution, the code in its `finally{}` blocks may never get
a chance to run, and as a result, it is difficult to write code that always cleans up after itself.

This hard limitation of the JavaScript runtime is called the [Await Event Horizon][await-event-horizon],
and developers experience its impact on daily basis. For example, the very common [EADDRINUSE][eaddrinuse-error]
error is caused by a caller not being able to execute clean up when a Node.js process is stopped.

By contrast, Effection _does_ provide this guarantee.

```js {7} showLineNumbers
import { main, action } from "effection";

await main(function*() {
try {
yield* action(function*(resolve) { setTimeout(resolve, 100,000) });
} finally {
// code here is GUARANTEED to run
}
});
```

When executing Effection operations you can expect that
they will run to completion; giving every operation an opportunity to clean up. At first glance, this
might seem like a small detail but it's fundamental to writing composable code.

## It's just JavaScript

Effection is designed to provide Structured Concurrency guarantees using common JavaScript language constructs such as
`let`, `const`, `if`, `for`, `while`, `switch` and `try/catch/finally`. Our goal is to allow JavaScript developers to
leverage what they already know while gaining the guarantees of Structured Concurrency. You can use all of these
constructs in an Effection function and they'll behave as you'd expect.

However, one of the constructs we avoid in Effection is the syntax and semantics of `async/await`.
This is because `async/await` is [not capable of modeling structured concurrency][await-event-horizon]. Instead, we make
extensive use of [generator functions][generator-functions] which are a core feature of JavaScript and are supported by all browsers
and JavaScript runtimes.

Finally, we provide a handy Effection Rosetta Stone to show how _Async/Await_ concepts map into Effection APIs.

[generator-functions]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*
[await-event-horizon]: https://frontside.com/blog/2023-12-11-await-event-horizon/
[delimited-continuation-repo]: https://github.com/thefrontside/continuation
[eaddrinuse-error]: https://stackoverflow.com/questions/14790910/stop-all-instances-of-node-js-server

0 comments on commit 4fa896a

Please sign in to comment.